From 46818ff3fabbe3939701ee2f00ab4501ee27992a Mon Sep 17 00:00:00 2001 From: Bryan Thornbury Date: Mon, 22 Aug 2016 12:21:34 -0700 Subject: [PATCH 1/8] Microsoft.DotNet.ProjectJsonMigration core library --- .../DefaultMigrationRuleSet.cs | 41 ++ .../MSBuildExtensions.cs | 114 +++++ ...icrosoft.DotNet.ProjectJsonMigration.xproj | 18 + .../MigrationRuleInputs.cs | 46 ++ .../MigrationSettings.cs | 58 +++ .../MigrationTrace.cs | 45 ++ .../Models/ItemMetadataValue.cs | 32 ++ .../Models/ProjectProperty.cs | 14 + .../ProjectMigrator.cs | 83 ++++ .../Properties/AssemblyInfo.cs | 3 + .../Rules/IMigrationRule.cs | 21 + .../Rules/MigrateBuildOptionsRule.cs | 405 ++++++++++++++++++ .../Rules/MigrateConfigurationsRule.cs | 70 +++ .../Rules/MigrateProjectDependenciesRule.cs | 95 ++++ .../Rules/MigratePublishOptionsRule.cs | 52 +++ .../Rules/MigrateRootOptionsRule.cs | 73 ++++ .../Rules/MigrateRuntimeOptionsRule.cs | 38 ++ .../Rules/MigrateScriptsRule.cs | 229 ++++++++++ .../Rules/MigrateTFMRule.cs | 93 ++++ .../MigrateXprojProjectReferencesRule.cs | 16 + .../Rules/SaveOutputProjectRule.cs | 29 ++ .../Rules/TemporaryMutateProjectJsonRule.cs | 98 +++++ .../project.json | 32 ++ .../transforms/AddBoolPropertyTransform.cs | 25 ++ .../transforms/AddItemTransform.cs | 124 ++++++ .../transforms/AddPropertyTransform.cs | 54 +++ .../transforms/AddStringPropertyTransform.cs | 25 ++ .../transforms/ConditionalTransform.cs | 38 ++ .../transforms/ITransform.cs | 9 + .../transforms/ITransformApplicator.cs | 26 ++ .../transforms/IncludeContextTransform.cs | 144 +++++++ .../transforms/TransformApplicator.cs | 170 ++++++++ 32 files changed, 2320 insertions(+) create mode 100644 src/Microsoft.DotNet.ProjectJsonMigration/DefaultMigrationRuleSet.cs create mode 100644 src/Microsoft.DotNet.ProjectJsonMigration/MSBuildExtensions.cs create mode 100644 src/Microsoft.DotNet.ProjectJsonMigration/Microsoft.DotNet.ProjectJsonMigration.xproj create mode 100644 src/Microsoft.DotNet.ProjectJsonMigration/MigrationRuleInputs.cs create mode 100644 src/Microsoft.DotNet.ProjectJsonMigration/MigrationSettings.cs create mode 100644 src/Microsoft.DotNet.ProjectJsonMigration/MigrationTrace.cs create mode 100644 src/Microsoft.DotNet.ProjectJsonMigration/Models/ItemMetadataValue.cs create mode 100644 src/Microsoft.DotNet.ProjectJsonMigration/Models/ProjectProperty.cs create mode 100644 src/Microsoft.DotNet.ProjectJsonMigration/ProjectMigrator.cs create mode 100644 src/Microsoft.DotNet.ProjectJsonMigration/Properties/AssemblyInfo.cs create mode 100644 src/Microsoft.DotNet.ProjectJsonMigration/Rules/IMigrationRule.cs create mode 100644 src/Microsoft.DotNet.ProjectJsonMigration/Rules/MigrateBuildOptionsRule.cs create mode 100644 src/Microsoft.DotNet.ProjectJsonMigration/Rules/MigrateConfigurationsRule.cs create mode 100644 src/Microsoft.DotNet.ProjectJsonMigration/Rules/MigrateProjectDependenciesRule.cs create mode 100644 src/Microsoft.DotNet.ProjectJsonMigration/Rules/MigratePublishOptionsRule.cs create mode 100644 src/Microsoft.DotNet.ProjectJsonMigration/Rules/MigrateRootOptionsRule.cs create mode 100644 src/Microsoft.DotNet.ProjectJsonMigration/Rules/MigrateRuntimeOptionsRule.cs create mode 100644 src/Microsoft.DotNet.ProjectJsonMigration/Rules/MigrateScriptsRule.cs create mode 100644 src/Microsoft.DotNet.ProjectJsonMigration/Rules/MigrateTFMRule.cs create mode 100644 src/Microsoft.DotNet.ProjectJsonMigration/Rules/MigrateXprojProjectReferencesRule.cs create mode 100644 src/Microsoft.DotNet.ProjectJsonMigration/Rules/SaveOutputProjectRule.cs create mode 100644 src/Microsoft.DotNet.ProjectJsonMigration/Rules/TemporaryMutateProjectJsonRule.cs create mode 100644 src/Microsoft.DotNet.ProjectJsonMigration/project.json create mode 100644 src/Microsoft.DotNet.ProjectJsonMigration/transforms/AddBoolPropertyTransform.cs create mode 100644 src/Microsoft.DotNet.ProjectJsonMigration/transforms/AddItemTransform.cs create mode 100644 src/Microsoft.DotNet.ProjectJsonMigration/transforms/AddPropertyTransform.cs create mode 100644 src/Microsoft.DotNet.ProjectJsonMigration/transforms/AddStringPropertyTransform.cs create mode 100644 src/Microsoft.DotNet.ProjectJsonMigration/transforms/ConditionalTransform.cs create mode 100644 src/Microsoft.DotNet.ProjectJsonMigration/transforms/ITransform.cs create mode 100644 src/Microsoft.DotNet.ProjectJsonMigration/transforms/ITransformApplicator.cs create mode 100644 src/Microsoft.DotNet.ProjectJsonMigration/transforms/IncludeContextTransform.cs create mode 100644 src/Microsoft.DotNet.ProjectJsonMigration/transforms/TransformApplicator.cs diff --git a/src/Microsoft.DotNet.ProjectJsonMigration/DefaultMigrationRuleSet.cs b/src/Microsoft.DotNet.ProjectJsonMigration/DefaultMigrationRuleSet.cs new file mode 100644 index 000000000..be900ff02 --- /dev/null +++ b/src/Microsoft.DotNet.ProjectJsonMigration/DefaultMigrationRuleSet.cs @@ -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); + } + } + } +} diff --git a/src/Microsoft.DotNet.ProjectJsonMigration/MSBuildExtensions.cs b/src/Microsoft.DotNet.ProjectJsonMigration/MSBuildExtensions.cs new file mode 100644 index 000000000..a25306535 --- /dev/null +++ b/src/Microsoft.DotNet.ProjectJsonMigration/MSBuildExtensions.cs @@ -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 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 ItemsWithoutConditions( + this ProjectRootElement projectRoot) + { + return projectRoot.Items + .Where(p => string.IsNullOrEmpty(p.Condition) + && p.AllParents.All(parent => string.IsNullOrEmpty(parent.Condition))); + } + + public static IEnumerable Includes( + this ProjectItemElement item) + { + return item.Include.Equals(string.Empty) ? Enumerable.Empty() : item.Include.Split(';'); + } + + public static IEnumerable Excludes( + this ProjectItemElement item) + { + return item.Exclude.Equals(string.Empty) ? Enumerable.Empty() : item.Exclude.Split(';'); + } + + public static IEnumerable AllConditions(this ProjectElement projectElement) + { + return new string[] { projectElement.Condition }.Concat(projectElement.AllParents.Select(p=> p.Condition)); + } + + public static IEnumerable CommonIncludes(this ProjectItemElement item, ProjectItemElement otherItem) + { + return item.Includes().Intersect(otherItem.Includes()); + } + + public static void RemoveIncludes(this ProjectItemElement item, IEnumerable includesToRemove) + { + item.Include = string.Join(";", item.Includes().Except(includesToRemove)); + } + + public static void AddIncludes(this ProjectItemElement item, IEnumerable includes) + { + item.Include = string.Join(";", item.Includes().Union(includes)); + } + + public static void AddExcludes(this ProjectItemElement item, IEnumerable 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 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); + } + } + + } +} diff --git a/src/Microsoft.DotNet.ProjectJsonMigration/Microsoft.DotNet.ProjectJsonMigration.xproj b/src/Microsoft.DotNet.ProjectJsonMigration/Microsoft.DotNet.ProjectJsonMigration.xproj new file mode 100644 index 000000000..0bc54ed18 --- /dev/null +++ b/src/Microsoft.DotNet.ProjectJsonMigration/Microsoft.DotNet.ProjectJsonMigration.xproj @@ -0,0 +1,18 @@ + + + + 14.0 + $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) + + + + 0E083818-2320-4388-8007-4F720FD5C634 + Microsoft.DotNet.ProjectJsonMigration + ..\..\artifacts\obj\$(MSBuildProjectName) + ..\..\artifacts\bin + + + 2.0 + + + \ No newline at end of file diff --git a/src/Microsoft.DotNet.ProjectJsonMigration/MigrationRuleInputs.cs b/src/Microsoft.DotNet.ProjectJsonMigration/MigrationRuleInputs.cs new file mode 100644 index 000000000..45dde00d0 --- /dev/null +++ b/src/Microsoft.DotNet.ProjectJsonMigration/MigrationRuleInputs.cs @@ -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 ProjectContexts { get; } + + public ProjectContext DefaultProjectContext + { + get + { + return ProjectContexts.First(); + } + } + + public MigrationRuleInputs( + IEnumerable projectContexts, + ProjectRootElement outputProject, + ProjectItemGroupElement commonItemGroup, + ProjectPropertyGroupElement commonPropertyGroup) + { + ProjectContexts = projectContexts; + OutputMSBuildProject = outputProject; + CommonItemGroup = commonItemGroup; + CommonPropertyGroup = commonPropertyGroup; + } + } +} diff --git a/src/Microsoft.DotNet.ProjectJsonMigration/MigrationSettings.cs b/src/Microsoft.DotNet.ProjectJsonMigration/MigrationSettings.cs new file mode 100644 index 000000000..10d7646ba --- /dev/null +++ b/src/Microsoft.DotNet.ProjectJsonMigration/MigrationSettings.cs @@ -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); + } + } +} diff --git a/src/Microsoft.DotNet.ProjectJsonMigration/MigrationTrace.cs b/src/Microsoft.DotNet.ProjectJsonMigration/MigrationTrace.cs new file mode 100644 index 000000000..05cd7e7db --- /dev/null +++ b/src/Microsoft.DotNet.ProjectJsonMigration/MigrationTrace.cs @@ -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); + } + } + } +} \ No newline at end of file diff --git a/src/Microsoft.DotNet.ProjectJsonMigration/Models/ItemMetadataValue.cs b/src/Microsoft.DotNet.ProjectJsonMigration/Models/ItemMetadataValue.cs new file mode 100644 index 000000000..536defd6d --- /dev/null +++ b/src/Microsoft.DotNet.ProjectJsonMigration/Models/ItemMetadataValue.cs @@ -0,0 +1,32 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; + +namespace Microsoft.DotNet.ProjectJsonMigration +{ + public class ItemMetadataValue + { + public string MetadataName { get; } + + private string _metadataValue; + private Func _metadataValueFunc; + + public ItemMetadataValue(string metadataName, string metadataValue) + { + MetadataName = metadataName; + _metadataValue = metadataValue; + } + + public ItemMetadataValue(string metadataName, Func metadataValueFunc) + { + MetadataName = metadataName; + _metadataValueFunc = metadataValueFunc; + } + + public string GetMetadataValue(T source) + { + return _metadataValue ?? _metadataValueFunc(source); + } + } +} diff --git a/src/Microsoft.DotNet.ProjectJsonMigration/Models/ProjectProperty.cs b/src/Microsoft.DotNet.ProjectJsonMigration/Models/ProjectProperty.cs new file mode 100644 index 000000000..9b7ddc395 --- /dev/null +++ b/src/Microsoft.DotNet.ProjectJsonMigration/Models/ProjectProperty.cs @@ -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; + } + } +} \ No newline at end of file diff --git a/src/Microsoft.DotNet.ProjectJsonMigration/ProjectMigrator.cs b/src/Microsoft.DotNet.ProjectJsonMigration/ProjectMigrator.cs new file mode 100644 index 000000000..b89c9f03b --- /dev/null +++ b/src/Microsoft.DotNet.ProjectJsonMigration/ProjectMigrator.cs @@ -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 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."); + } + } + + + } +} diff --git a/src/Microsoft.DotNet.ProjectJsonMigration/Properties/AssemblyInfo.cs b/src/Microsoft.DotNet.ProjectJsonMigration/Properties/AssemblyInfo.cs new file mode 100644 index 000000000..9ede032e7 --- /dev/null +++ b/src/Microsoft.DotNet.ProjectJsonMigration/Properties/AssemblyInfo.cs @@ -0,0 +1,3 @@ +using System.Runtime.CompilerServices; + +[assembly: InternalsVisibleTo("Microsoft.DotNet.ProjectJsonMigration.Tests")] diff --git a/src/Microsoft.DotNet.ProjectJsonMigration/Rules/IMigrationRule.cs b/src/Microsoft.DotNet.ProjectJsonMigration/Rules/IMigrationRule.cs new file mode 100644 index 000000000..a6283e180 --- /dev/null +++ b/src/Microsoft.DotNet.ProjectJsonMigration/Rules/IMigrationRule.cs @@ -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); + } +} diff --git a/src/Microsoft.DotNet.ProjectJsonMigration/Rules/MigrateBuildOptionsRule.cs b/src/Microsoft.DotNet.ProjectJsonMigration/Rules/MigrateBuildOptionsRule.cs new file mode 100644 index 000000000..c66ce8005 --- /dev/null +++ b/src/Microsoft.DotNet.ProjectJsonMigration/Rules/MigrateBuildOptionsRule.cs @@ -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[] EmitEntryPointTransforms + => new AddPropertyTransform[] + { + new AddPropertyTransform("OutputType", "Exe", + compilerOptions => compilerOptions.EmitEntryPoint != null && compilerOptions.EmitEntryPoint.Value), + new AddPropertyTransform("TargetExt", ".dll", + compilerOptions => compilerOptions.EmitEntryPoint != null && compilerOptions.EmitEntryPoint.Value), + new AddPropertyTransform("OutputType", "Library", + compilerOptions => compilerOptions.EmitEntryPoint == null || !compilerOptions.EmitEntryPoint.Value) + }; + + private AddPropertyTransform[] KeyFileTransforms + => new AddPropertyTransform[] + { + new AddPropertyTransform("AssemblyOriginatorKeyFile", + compilerOptions => compilerOptions.KeyFile, + compilerOptions => !string.IsNullOrEmpty(compilerOptions.KeyFile)), + new AddPropertyTransform("SignAssembly", + "true", + compilerOptions => !string.IsNullOrEmpty(compilerOptions.KeyFile)) + }; + + private AddPropertyTransform DefineTransform => new AddPropertyTransform( + "DefineConstants", + compilerOptions => string.Join(";", compilerOptions.Defines), + compilerOptions => compilerOptions.Defines != null && compilerOptions.Defines.Any()); + + private AddPropertyTransform NoWarnTransform => new AddPropertyTransform( + "NoWarn", + compilerOptions => string.Join(";", compilerOptions.SuppressWarnings), + compilerOptions => compilerOptions.SuppressWarnings != null && compilerOptions.SuppressWarnings.Any()); + + private AddPropertyTransform PreserveCompilationContextTransform => + new AddPropertyTransform("PreserveCompilationContext", + compilerOptions => compilerOptions.PreserveCompilationContext.ToString().ToLower(), + compilerOptions => compilerOptions.PreserveCompilationContext != null && compilerOptions.PreserveCompilationContext.Value); + + private AddPropertyTransform WarningsAsErrorsTransform => + new AddPropertyTransform("WarningsAsErrors", + compilerOptions => compilerOptions.WarningsAsErrors.ToString().ToLower(), + compilerOptions => compilerOptions.WarningsAsErrors != null && compilerOptions.WarningsAsErrors.Value); + + private AddPropertyTransform AllowUnsafeTransform => + new AddPropertyTransform("AllowUnsafeBlocks", + compilerOptions => compilerOptions.AllowUnsafe.ToString().ToLower(), + compilerOptions => compilerOptions.AllowUnsafe != null && compilerOptions.AllowUnsafe.Value); + + private AddPropertyTransform OptimizeTransform => + new AddPropertyTransform("Optimize", + compilerOptions => compilerOptions.Optimize.ToString().ToLower(), + compilerOptions => compilerOptions.Optimize != null && compilerOptions.Optimize.Value); + + private AddPropertyTransform PlatformTransform => + new AddPropertyTransform("PlatformTarget", + compilerOptions => compilerOptions.Platform, + compilerOptions => !string.IsNullOrEmpty(compilerOptions.Platform)); + + private AddPropertyTransform LanguageVersionTransform => + new AddPropertyTransform("LangVersion", + compilerOptions => compilerOptions.LanguageVersion, + compilerOptions => !string.IsNullOrEmpty(compilerOptions.LanguageVersion)); + + private AddPropertyTransform DelaySignTransform => + new AddPropertyTransform("DelaySign", + compilerOptions => compilerOptions.DelaySign.ToString().ToLower(), + compilerOptions => compilerOptions.DelaySign != null && compilerOptions.DelaySign.Value); + + private AddPropertyTransform PublicSignTransform => + new AddPropertyTransform("PublicSign", + compilerOptions => compilerOptions.PublicSign.ToString().ToLower(), + compilerOptions => compilerOptions.PublicSign != null && compilerOptions.PublicSign.Value); + + private AddPropertyTransform DebugTypeTransform => + new AddPropertyTransform("DebugType", + compilerOptions => compilerOptions.DebugType, + compilerOptions => !string.IsNullOrEmpty(compilerOptions.DebugType)); + + private AddPropertyTransform XmlDocTransform => + new AddPropertyTransform("GenerateDocumentationFile", + compilerOptions => compilerOptions.GenerateXmlDocumentation.ToString().ToLower(), + compilerOptions => compilerOptions.GenerateXmlDocumentation != null && compilerOptions.GenerateXmlDocumentation.Value); + + // TODO: https://github.com/dotnet/sdk/issues/67 + private AddPropertyTransform XmlDocTransformFilePath => + new AddPropertyTransform("DocumentationFile", + @"$(OutputPath)\$(AssemblyName).xml", + compilerOptions => compilerOptions.GenerateXmlDocumentation != null && compilerOptions.GenerateXmlDocumentation.Value); + + private AddPropertyTransform OutputNameTransform => + new AddPropertyTransform("AssemblyName", + compilerOptions => compilerOptions.OutputName, + compilerOptions => !string.IsNullOrEmpty(compilerOptions.OutputName)); + + private IncludeContextTransform CompileFilesTransform => + new IncludeContextTransform("Compile", transformMappings: false); + + private IncludeContextTransform EmbedFilesTransform => + new IncludeContextTransform("EmbeddedResource", transformMappings: false); + + private IncludeContextTransform CopyToOutputFilesTransform => + new IncludeContextTransform("Content", transformMappings: true) + .WithMetadata("CopyToOutputDirectory", "PreserveNewest"); + + private Func> CompileFilesTransformExecute => + (compilerOptions, projectDirectory) => + CompileFilesTransform.Transform(GetCompileIncludeContext(compilerOptions, projectDirectory)); + + private Func> EmbedFilesTransformExecute => + (compilerOptions, projectDirectory) => + EmbedFilesTransform.Transform(GetEmbedIncludeContext(compilerOptions, projectDirectory)); + + private Func> CopyToOutputFilesTransformExecute => + (compilerOptions, projectDirectory) => + CopyToOutputFilesTransform.Transform(GetCopyToOutputIncludeContext(compilerOptions, projectDirectory)); + + private string _configuration; + private ProjectPropertyGroupElement _configurationPropertyGroup; + private ProjectItemGroupElement _configurationItemGroup; + + private List> _propertyTransforms; + private List>> _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>() + { + DefineTransform, + NoWarnTransform, + WarningsAsErrorsTransform, + AllowUnsafeTransform, + OptimizeTransform, + PlatformTransform, + LanguageVersionTransform, + DelaySignTransform, + PublicSignTransform, + DebugTypeTransform, + OutputNameTransform, + XmlDocTransform, + XmlDocTransformFilePath, + PreserveCompilationContextTransform + }; + + _propertyTransforms.AddRange(EmitEntryPointTransforms); + _propertyTransforms.AddRange(KeyFileTransforms); + + _includeContextTransformExecutes = new List>>() + { + 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 configurationOutput, IEnumerable nonConfigurationOutput) + { + foreach (var item1 in configurationOutput) + { + if (item1 == null) + { + continue; + } + + var item2Excludes = new HashSet(); + 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 itemsToRemoveFrom, + IEnumerable 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); + } + } +} diff --git a/src/Microsoft.DotNet.ProjectJsonMigration/Rules/MigrateConfigurationsRule.cs b/src/Microsoft.DotNet.ProjectJsonMigration/Rules/MigrateConfigurationsRule.cs new file mode 100644 index 000000000..c9b85916c --- /dev/null +++ b/src/Microsoft.DotNet.ProjectJsonMigration/Rules/MigrateConfigurationsRule.cs @@ -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; + } + } +} diff --git a/src/Microsoft.DotNet.ProjectJsonMigration/Rules/MigrateProjectDependenciesRule.cs b/src/Microsoft.DotNet.ProjectJsonMigration/Rules/MigrateProjectDependenciesRule.cs new file mode 100644 index 000000000..0b78b044e --- /dev/null +++ b/src/Microsoft.DotNet.ProjectJsonMigration/Rules/MigrateProjectDependenciesRule.cs @@ -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 AutoUnifyTransform => new AddPropertyTransform( + "AutoUnify", + "true", + b => true); + + private AddPropertyTransform DesignTimeAutoUnifyTransform => new AddPropertyTransform( + "DesignTimeAutoUnify", + "true", + b => true); + + private AddItemTransform ProjectDependencyTransform => new AddItemTransform( + "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); + } +} diff --git a/src/Microsoft.DotNet.ProjectJsonMigration/Rules/MigratePublishOptionsRule.cs b/src/Microsoft.DotNet.ProjectJsonMigration/Rules/MigratePublishOptionsRule.cs new file mode 100644 index 000000000..52a00095e --- /dev/null +++ b/src/Microsoft.DotNet.ProjectJsonMigration/Rules/MigratePublishOptionsRule.cs @@ -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"); + } +} diff --git a/src/Microsoft.DotNet.ProjectJsonMigration/Rules/MigrateRootOptionsRule.cs b/src/Microsoft.DotNet.ProjectJsonMigration/Rules/MigrateRootOptionsRule.cs new file mode 100644 index 000000000..357c1901e --- /dev/null +++ b/src/Microsoft.DotNet.ProjectJsonMigration/Rules/MigrateRootOptionsRule.cs @@ -0,0 +1,73 @@ +// Copyright (c) .NET Foundation and contributors. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System; +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[] _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 DescriptionTransform => new AddPropertyTransform("Description", + project => project.Description, + project => !string.IsNullOrEmpty(project.Description)); + + private AddPropertyTransform CopyrightTransform => new AddPropertyTransform("Copyright", + project => project.Copyright, + project => !string.IsNullOrEmpty(project.Copyright)); + + private AddPropertyTransform TitleTransform => new AddPropertyTransform("AssemblyTitle", + project => project.Title, + project => !string.IsNullOrEmpty(project.Title)); + + private AddPropertyTransform LanguageTransform => new AddPropertyTransform("NeutralLanguage", + project => project.Language, + project => !string.IsNullOrEmpty(project.Language)); + + private AddPropertyTransform VersionTransform => new AddPropertyTransform("VersionPrefix", + project => project.Version.ToString(), p => true); + } +} diff --git a/src/Microsoft.DotNet.ProjectJsonMigration/Rules/MigrateRuntimeOptionsRule.cs b/src/Microsoft.DotNet.ProjectJsonMigration/Rules/MigrateRuntimeOptionsRule.cs new file mode 100644 index 000000000..3b59c7a63 --- /dev/null +++ b/src/Microsoft.DotNet.ProjectJsonMigration/Rules/MigrateRuntimeOptionsRule.cs @@ -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); + } + } + } +} diff --git a/src/Microsoft.DotNet.ProjectJsonMigration/Rules/MigrateScriptsRule.cs b/src/Microsoft.DotNet.ProjectJsonMigration/Rules/MigrateScriptsRule.cs new file mode 100644 index 000000000..9eb18cf72 --- /dev/null +++ b/src/Microsoft.DotNet.ProjectJsonMigration/Rules/MigrateScriptsRule.cs @@ -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 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 ScriptSetToMSBuildHookTargetMap => new Dictionary() + { + { "precompile", new TargetHookInfo(true, "Build") }, + { "postcompile", new TargetHookInfo(false, "Build") }, + { "prepublish", new TargetHookInfo(true, "Publish") }, + { "postpublish", new TargetHookInfo(false, "Publish") } + }; + + private static Dictionary ScriptVariableToMSBuildMap => new Dictionary() + { + { "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; + } + } + } +} diff --git a/src/Microsoft.DotNet.ProjectJsonMigration/Rules/MigrateTFMRule.cs b/src/Microsoft.DotNet.ProjectJsonMigration/Rules/MigrateTFMRule.cs new file mode 100644 index 000000000..343c33667 --- /dev/null +++ b/src/Microsoft.DotNet.ProjectJsonMigration/Rules/MigrateTFMRule.cs @@ -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[] _transforms; + + public MigrateTFMRule(TransformApplicator transformApplicator = null) + { + _transformApplicator = transformApplicator ?? new TransformApplicator(); + + _transforms = new AddPropertyTransform[] + { + 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 OutputPathTransform => + new AddPropertyTransform("OutputPath", + f => $"bin/$(Configuration)/{f.GetShortFolderName()}", + f => true); + + private AddPropertyTransform FrameworkIdentifierTransform => + new AddPropertyTransform("TargetFrameworkIdentifier", + f => f.Framework, + f => true); + + private AddPropertyTransform FrameworkVersionTransform => + new AddPropertyTransform("TargetFrameworkVersion", + f => "v" + GetDisplayVersion(f.Version), + f => true); + } +} diff --git a/src/Microsoft.DotNet.ProjectJsonMigration/Rules/MigrateXprojProjectReferencesRule.cs b/src/Microsoft.DotNet.ProjectJsonMigration/Rules/MigrateXprojProjectReferencesRule.cs new file mode 100644 index 000000000..1b5eee495 --- /dev/null +++ b/src/Microsoft.DotNet.ProjectJsonMigration/Rules/MigrateXprojProjectReferencesRule.cs @@ -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(); + } + } +} diff --git a/src/Microsoft.DotNet.ProjectJsonMigration/Rules/SaveOutputProjectRule.cs b/src/Microsoft.DotNet.ProjectJsonMigration/Rules/SaveOutputProjectRule.cs new file mode 100644 index 000000000..adfba4fbb --- /dev/null +++ b/src/Microsoft.DotNet.ProjectJsonMigration/Rules/SaveOutputProjectRule.cs @@ -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); + } + } +} diff --git a/src/Microsoft.DotNet.ProjectJsonMigration/Rules/TemporaryMutateProjectJsonRule.cs b/src/Microsoft.DotNet.ProjectJsonMigration/Rules/TemporaryMutateProjectJsonRule.cs new file mode 100644 index 000000000..c15f751b6 --- /dev/null +++ b/src/Microsoft.DotNet.ProjectJsonMigration/Rules/TemporaryMutateProjectJsonRule.cs @@ -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 +{ + /// + /// 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. + /// + 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(); + 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 + { + public AddBoolPropertyTransform(string propertyName) + : base(propertyName, b => b.ToString(), b => b) { } + + public AddBoolPropertyTransform(string propertyName, Func condition) + : base(propertyName, b => b.ToString(), condition) { } + } +} diff --git a/src/Microsoft.DotNet.ProjectJsonMigration/transforms/AddItemTransform.cs b/src/Microsoft.DotNet.ProjectJsonMigration/transforms/AddItemTransform.cs new file mode 100644 index 000000000..fde54f3ed --- /dev/null +++ b/src/Microsoft.DotNet.ProjectJsonMigration/transforms/AddItemTransform.cs @@ -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 : ConditionalTransform + { + private ProjectRootElement _itemObjectGenerator = ProjectRootElement.Create(); + + private string _itemName; + private string _includeValue; + private string _excludeValue; + + private Func _includeValueFunc; + private Func _excludeValueFunc; + + private bool _mergeExisting; + + private List> _metadata = new List>(); + + public AddItemTransform( + string itemName, + IEnumerable includeValues, + IEnumerable excludeValues, + Func condition, + bool mergeExisting = false) + : this(itemName, string.Join(";", includeValues), string.Join(";", excludeValues), condition, mergeExisting) { } + + public AddItemTransform( + string itemName, + Func includeValueFunc, + Func excludeValueFunc, + Func condition) + : base(condition) + { + _itemName = itemName; + _includeValueFunc = includeValueFunc; + _excludeValueFunc = excludeValueFunc; + } + + public AddItemTransform( + string itemName, + string includeValue, + Func excludeValueFunc, + Func condition) + : base(condition) + { + _itemName = itemName; + _includeValue = includeValue; + _excludeValueFunc = excludeValueFunc; + } + + public AddItemTransform( + string itemName, + Func includeValueFunc, + string excludeValue, + Func condition) + : base(condition) + { + _itemName = itemName; + _includeValueFunc = includeValueFunc; + _excludeValue = excludeValue; + } + + public AddItemTransform( + string itemName, + string includeValue, + string excludeValue, + Func condition, + bool mergeExisting=false) + : base(condition) + { + _itemName = itemName; + _includeValue = includeValue; + _excludeValue = excludeValue; + _mergeExisting = mergeExisting; + } + + public AddItemTransform WithMetadata(string metadataName, string metadataValue) + { + _metadata.Add(new ItemMetadataValue(metadataName, metadataValue)); + return this; + } + + public AddItemTransform WithMetadata(string metadataName, Func metadataValueFunc) + { + _metadata.Add(new ItemMetadataValue(metadataName, metadataValueFunc)); + return this; + } + + public AddItemTransform WithMetadata(ItemMetadataValue 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; + } + } +} diff --git a/src/Microsoft.DotNet.ProjectJsonMigration/transforms/AddPropertyTransform.cs b/src/Microsoft.DotNet.ProjectJsonMigration/transforms/AddPropertyTransform.cs new file mode 100644 index 000000000..48231ee40 --- /dev/null +++ b/src/Microsoft.DotNet.ProjectJsonMigration/transforms/AddPropertyTransform.cs @@ -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 : ConditionalTransform + { + public string PropertyName { get; } + + private readonly ProjectRootElement _propertyObjectGenerator = ProjectRootElement.Create(); + private readonly string _propertyValue; + private readonly Func _propertyValueFunc; + + public AddPropertyTransform(string propertyName, string propertyValue, Func condition) + : base(condition) + { + PropertyName = propertyName; + _propertyValue = propertyValue; + } + + public AddPropertyTransform(string propertyName, Func propertyValueFunc, Func 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); + } + } +} diff --git a/src/Microsoft.DotNet.ProjectJsonMigration/transforms/AddStringPropertyTransform.cs b/src/Microsoft.DotNet.ProjectJsonMigration/transforms/AddStringPropertyTransform.cs new file mode 100644 index 000000000..facca2e4d --- /dev/null +++ b/src/Microsoft.DotNet.ProjectJsonMigration/transforms/AddStringPropertyTransform.cs @@ -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 + { + public AddStringPropertyTransform(string propertyName) + : base(propertyName, s => s, s => !string.IsNullOrEmpty(s)) { } + + public AddStringPropertyTransform(string propertyName, Func condition) + : base(propertyName, s => s, s => !string.IsNullOrEmpty(s) && condition(s)) { } + } +} diff --git a/src/Microsoft.DotNet.ProjectJsonMigration/transforms/ConditionalTransform.cs b/src/Microsoft.DotNet.ProjectJsonMigration/transforms/ConditionalTransform.cs new file mode 100644 index 000000000..0ccd10225 --- /dev/null +++ b/src/Microsoft.DotNet.ProjectJsonMigration/transforms/ConditionalTransform.cs @@ -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 : ITransform + { + private Func _condition; + + public ConditionalTransform(Func 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); + } +} diff --git a/src/Microsoft.DotNet.ProjectJsonMigration/transforms/ITransform.cs b/src/Microsoft.DotNet.ProjectJsonMigration/transforms/ITransform.cs new file mode 100644 index 000000000..ab9b37804 --- /dev/null +++ b/src/Microsoft.DotNet.ProjectJsonMigration/transforms/ITransform.cs @@ -0,0 +1,9 @@ +using Microsoft.Build.Construction; + +namespace Microsoft.DotNet.ProjectJsonMigration +{ + public interface ITransform + { + U Transform(T source); + } +} \ No newline at end of file diff --git a/src/Microsoft.DotNet.ProjectJsonMigration/transforms/ITransformApplicator.cs b/src/Microsoft.DotNet.ProjectJsonMigration/transforms/ITransformApplicator.cs new file mode 100644 index 000000000..b2c7bf307 --- /dev/null +++ b/src/Microsoft.DotNet.ProjectJsonMigration/transforms/ITransformApplicator.cs @@ -0,0 +1,26 @@ +using System.Collections.Generic; +using Microsoft.Build.Construction; + +namespace Microsoft.DotNet.ProjectJsonMigration +{ + public interface ITransformApplicator + { + void Execute( + T element, + U destinationElement) where T : ProjectElement where U : ProjectElementContainer; + + void Execute( + IEnumerable elements, + U destinationElement) where T : ProjectElement where U : ProjectElementContainer; + + void Execute( + ProjectItemElement item, + ProjectItemGroupElement destinationItemGroup, + bool mergeExisting); + + void Execute( + IEnumerable items, + ProjectItemGroupElement destinationItemGroup, + bool mergeExisting); + } +} \ No newline at end of file diff --git a/src/Microsoft.DotNet.ProjectJsonMigration/transforms/IncludeContextTransform.cs b/src/Microsoft.DotNet.ProjectJsonMigration/transforms/IncludeContextTransform.cs new file mode 100644 index 000000000..c935d34e5 --- /dev/null +++ b/src/Microsoft.DotNet.ProjectJsonMigration/transforms/IncludeContextTransform.cs @@ -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> + { + // 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> _metadata = new List>(); + private AddItemTransform[] _transformSet; + + public IncludeContextTransform( + string itemName, + bool transformMappings = true, + Func condition = null) : base(condition) + { + _itemName = itemName; + _transformMappings = transformMappings; + + CreateTransformSet(); + } + + public IncludeContextTransform WithMetadata(string metadataName, string metadataValue) + { + _metadata.Add(new ItemMetadataValue(metadataName, metadataValue)); + return this; + } + + public IncludeContextTransform WithMetadata(string metadataName, Func metadataValueFunc) + { + _metadata.Add(new ItemMetadataValue(metadataName, metadataValueFunc)); + return this; + } + + private void CreateTransformSet() + { + var includeFilesExcludeFilesTransformation = new AddItemTransform( + _itemName, + includeContext => FormatPatterns(includeContext.IncludeFiles, includeContext.SourceBasePath), + includeContext => FormatPatterns(includeContext.ExcludeFiles, includeContext.SourceBasePath), + includeContext => includeContext != null && includeContext.IncludeFiles.Count > 0); + + var includeExcludeTransformation = new AddItemTransform( + _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[] + { + includeFilesExcludeFilesTransformation, + includeExcludeTransformation + }; + } + + private string FormatPatterns(IEnumerable patterns, string projectDirectory) + { + List mutatedPatterns = new List(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 ConditionallyTransform(IncludeContext source) + { + return _transformSet.Select(t => t.Transform(source)); + } + } +} diff --git a/src/Microsoft.DotNet.ProjectJsonMigration/transforms/TransformApplicator.cs b/src/Microsoft.DotNet.ProjectJsonMigration/transforms/TransformApplicator.cs new file mode 100644 index 000000000..6a13154a3 --- /dev/null +++ b/src/Microsoft.DotNet.ProjectJsonMigration/transforms/TransformApplicator.cs @@ -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 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( + IEnumerable 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 items, + ProjectItemGroupElement destinationItemGroup, + bool mergeExisting) + { + foreach (var item in items) + { + Execute(item, destinationItemGroup, mergeExisting); + } + } + + /// + /// Merges two items on their common sets of includes. + /// The output is 3 items, the 2 input items and the merged items. If the common + /// set of includes spans the entirety of the includes of either of the 2 input + /// items, that item will be returned as null. + /// + /// The 3rd output item, the merged item, will have the Union of the excludes and + /// metadata from the 2 input items. If any metadata between the 2 input items is different, + /// this will throw. + /// + /// This function will mutate the Include property of the 2 input items, removing the common subset. + /// + private MergeResult MergeItems(ProjectItemElement item, ProjectItemElement existingItem) + { + if (!string.Equals(item.ItemType, existingItem.ItemType, StringComparison.Ordinal)) + { + throw new InvalidOperationException("Cannot merge items of different types."); + } + + if (!item.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 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; } + } + } +} From 611e4ccfde8ee33c917070dadf57ae80b43a886c Mon Sep 17 00:00:00 2001 From: Bryan Thornbury Date: Mon, 22 Aug 2016 12:21:52 -0700 Subject: [PATCH 2/8] dotnet-migrate built in command for cli --- src/dotnet/Program.cs | 2 + src/dotnet/Properties/AssemblyInfo.cs | 1 + src/dotnet/commands/dotnet-build3/Program.cs | 1 - .../commands/dotnet-migrate/MigrateCommand.cs | 84 ++++++++++++++++++ src/dotnet/commands/dotnet-migrate/Program.cs | 55 ++++++++++++ .../dotnet-migrate/ProjectJsonParser.cs | 81 ++++++++++++++++++ .../TemporaryDotnetNewTemplateProject.cs | 85 +++++++++++++++++++ src/dotnet/project.json | 3 + 8 files changed, 311 insertions(+), 1 deletion(-) create mode 100644 src/dotnet/commands/dotnet-migrate/MigrateCommand.cs create mode 100644 src/dotnet/commands/dotnet-migrate/Program.cs create mode 100644 src/dotnet/commands/dotnet-migrate/ProjectJsonParser.cs create mode 100644 src/dotnet/commands/dotnet-migrate/TemporaryDotnetNewTemplateProject.cs diff --git a/src/dotnet/Program.cs b/src/dotnet/Program.cs index 19eb6eb81..60ee18d42 100644 --- a/src/dotnet/Program.cs +++ b/src/dotnet/Program.cs @@ -21,6 +21,7 @@ using Microsoft.DotNet.Tools.Restore; using Microsoft.DotNet.Tools.Restore3; using Microsoft.DotNet.Tools.Run; using Microsoft.DotNet.Tools.Test; +using Microsoft.DotNet.Tools.Migrate; using NuGet.Frameworks; namespace Microsoft.DotNet.Cli @@ -42,6 +43,7 @@ namespace Microsoft.DotNet.Cli ["run3"] = Run3Command.Run, ["restore3"] = Restore3Command.Run, ["pack3"] = Pack3Command.Run, + ["migrate"] = MigrateCommand.Run }; public static int Main(string[] args) diff --git a/src/dotnet/Properties/AssemblyInfo.cs b/src/dotnet/Properties/AssemblyInfo.cs index ffdc13024..c6553d456 100644 --- a/src/dotnet/Properties/AssemblyInfo.cs +++ b/src/dotnet/Properties/AssemblyInfo.cs @@ -3,3 +3,4 @@ using System.Runtime.CompilerServices; [assembly: AssemblyMetadataAttribute("Serviceable", "True")] [assembly: InternalsVisibleTo("dotnet.Tests")] +[assembly: InternalsVisibleTo("Microsoft.DotNet.ProjectJsonMigration.Tests")] \ No newline at end of file diff --git a/src/dotnet/commands/dotnet-build3/Program.cs b/src/dotnet/commands/dotnet-build3/Program.cs index e9efe9698..7630df3c3 100644 --- a/src/dotnet/commands/dotnet-build3/Program.cs +++ b/src/dotnet/commands/dotnet-build3/Program.cs @@ -15,6 +15,5 @@ namespace Microsoft.DotNet.Cli { return new MSBuildForwardingApp(args).Execute(); } - } } diff --git a/src/dotnet/commands/dotnet-migrate/MigrateCommand.cs b/src/dotnet/commands/dotnet-migrate/MigrateCommand.cs new file mode 100644 index 000000000..430e486c1 --- /dev/null +++ b/src/dotnet/commands/dotnet-migrate/MigrateCommand.cs @@ -0,0 +1,84 @@ +// 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.IO; +using Microsoft.Build.Construction; +using Microsoft.DotNet.Cli; +using Microsoft.DotNet.ProjectJsonMigration; + +namespace Microsoft.DotNet.Tools.Migrate +{ + public partial class MigrateCommand + { + private string _templateFile; + private string _outputDirectory; + private string _projectJson; + private string _sdkVersion; + + private TemporaryDotnetNewTemplateProject _temporaryDotnetNewProject; + + public MigrateCommand(string templateFile, string outputDirectory, string projectJson, string sdkVersion) + { + _templateFile = templateFile; + _outputDirectory = outputDirectory; + _projectJson = projectJson; + _sdkVersion = sdkVersion; + + _temporaryDotnetNewProject = new TemporaryDotnetNewTemplateProject(); + } + + public int Start() + { + var project = GetProjectJsonPath(_projectJson) ?? _temporaryDotnetNewProject.ProjectJsonPath; + EnsureNotNull(project, "Unable to find project.json"); + var projectDirectory = Path.GetDirectoryName(project); + + var templateFile = _templateFile ?? _temporaryDotnetNewProject.MSBuildProjectPath; + EnsureNotNull(templateFile, "Unable to find default msbuild template"); + + var outputDirectory = _outputDirectory ?? Path.GetDirectoryName(project); + EnsureNotNull(outputDirectory, "Null output directory"); + + var sdkVersion = _sdkVersion ?? new ProjectJsonParser(_temporaryDotnetNewProject.ProjectJson).SdkPackageVersion; + EnsureNotNull(sdkVersion, "Null Sdk Version"); + + var migrationSettings = new MigrationSettings(projectDirectory, outputDirectory, sdkVersion, templateFile); + new ProjectMigrator().Migrate(migrationSettings); + return 0; + } + + private void EnsureNotNull(string variable, string message) + { + if (variable == null) + { + throw new Exception(message); + } + } + + private string GetProjectJsonPath(string projectJson) + { + if (projectJson == null) + { + return null; + } + + if (File.Exists(projectJson)) + { + return projectJson; + } + + if (Directory.Exists(projectJson)) + { + var projectCandidate = Path.Combine(projectJson, "project.json"); + + if (File.Exists(projectCandidate)) + { + return projectCandidate; + } + } + + throw new Exception($"Unable to find project file at {projectJson}"); + } + } +} diff --git a/src/dotnet/commands/dotnet-migrate/Program.cs b/src/dotnet/commands/dotnet-migrate/Program.cs new file mode 100644 index 000000000..8c2c5708e --- /dev/null +++ b/src/dotnet/commands/dotnet-migrate/Program.cs @@ -0,0 +1,55 @@ +// 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 Microsoft.DotNet.Cli.CommandLine; +using Microsoft.DotNet.Cli.Utils; + +namespace Microsoft.DotNet.Tools.Migrate +{ + public partial class MigrateCommand + { + public static int Run(string[] args) + { + DebugHelper.HandleDebugSwitch(ref args); + + CommandLineApplication app = new CommandLineApplication(throwOnUnexpectedArg: false); + app.Name = "dotnet migrate"; + app.FullName = ".NET Migrate Command"; + app.Description = "Command used to migrate project.json projects to msbuild"; + app.HandleResponseFiles = true; + app.AllowArgumentSeparator = true; + app.HelpOption("-h|--help"); + + CommandOption template = app.Option("-t|--template-file", "Base MSBuild template to use for migrated app. The default is the project included in dotnet new -t msbuild", CommandOptionType.SingleValue); + CommandOption output = app.Option("-o|--output", "Directory to output migrated project to. The default is the project directory", CommandOptionType.SingleValue); + CommandOption project = app.Option("-p|--project", "The path to the project to run (defaults to the current directory). Can be a path to a project.json or a project directory", CommandOptionType.SingleValue); + CommandOption sdkVersion = app.Option("-v|--sdk-package-version", "The version of the sdk package that will be referenced in the migrated app. The default is the version of the sdk in dotnet new -t msbuild", CommandOptionType.SingleValue); + + app.OnExecute(() => + { + MigrateCommand migrateCommand = new MigrateCommand( + template.Value(), + output.Value(), + project.Value(), + sdkVersion.Value()); + + return migrateCommand.Start(); + }); + + try + { + return app.Execute(args); + } + catch (Exception ex) + { +#if DEBUG + Reporter.Error.WriteLine(ex.ToString()); +#else + Reporter.Error.WriteLine(ex.Message); +#endif + return 1; + } + } + } +} diff --git a/src/dotnet/commands/dotnet-migrate/ProjectJsonParser.cs b/src/dotnet/commands/dotnet-migrate/ProjectJsonParser.cs new file mode 100644 index 000000000..4856a64aa --- /dev/null +++ b/src/dotnet/commands/dotnet-migrate/ProjectJsonParser.cs @@ -0,0 +1,81 @@ +using Newtonsoft.Json.Linq; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Threading.Tasks; + +namespace Microsoft.DotNet.Cli +{ + /// + /// Parses select data from a project.json without relying on ProjectModel. + /// Used to parse simple information. + /// + internal class ProjectJsonParser + { + public static string SdkPackageName => "Microsoft.DotNet.Core.Sdk"; + + public string SdkPackageVersion { get; } + + public ProjectJsonParser(JObject projectJson) + { + SdkPackageVersion = GetSdkPackageVersion(projectJson); + } + + private string GetSdkPackageVersion(JObject projectJson) + { + var sdkPackageNode = SelectJsonNodes(projectJson, property => property.Name == SdkPackageName).First(); + + if (sdkPackageNode.Value.Type == JTokenType.String) + { + return (string)sdkPackageNode.Value; + } + else if (sdkPackageNode.Type == JTokenType.Object) + { + var sdkPackageNodeValue = (JObject)sdkPackageNode.Value; + + JToken sdkVersionNode; + if (sdkPackageNodeValue.TryGetValue("version", out sdkVersionNode)) + { + return sdkVersionNode.Value(); + } + else + { + throw new Exception("Unable to determine sdk version, no version node in default template."); + } + } + else + { + throw new Exception("Unable to determine sdk version, no version information found"); + } + } + + private IEnumerable SelectJsonNodes( + JToken jsonNode, + Func condition, + List nodeAccumulator = null) + { + nodeAccumulator = nodeAccumulator ?? new List(); + + if (jsonNode.Type == JTokenType.Object) + { + var eligibleNodes = jsonNode.Children().Where(j => condition(j)); + nodeAccumulator.AddRange(eligibleNodes); + + foreach (var child in jsonNode.Children()) + { + SelectJsonNodes(child.Value, condition, nodeAccumulator: nodeAccumulator); + } + } + else if (jsonNode.Type == JTokenType.Array) + { + foreach (var child in jsonNode.Children()) + { + SelectJsonNodes(child, condition, nodeAccumulator: nodeAccumulator); + } + } + + return nodeAccumulator; + } + } +} diff --git a/src/dotnet/commands/dotnet-migrate/TemporaryDotnetNewTemplateProject.cs b/src/dotnet/commands/dotnet-migrate/TemporaryDotnetNewTemplateProject.cs new file mode 100644 index 000000000..86a944c4b --- /dev/null +++ b/src/dotnet/commands/dotnet-migrate/TemporaryDotnetNewTemplateProject.cs @@ -0,0 +1,85 @@ +using Microsoft.Build.Construction; +using Microsoft.DotNet.Cli; +using Newtonsoft.Json.Linq; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Threading.Tasks; + +namespace Microsoft.DotNet.Cli +{ + internal class TemporaryDotnetNewTemplateProject + { + private static string s_temporaryDotnetNewMSBuildProjectName = "p"; + + public TemporaryDotnetNewTemplateProject() + { + ProjectDirectory = CreateDotnetNewMSBuild(s_temporaryDotnetNewMSBuildProjectName); + MSBuildProject = GetMSBuildProject(ProjectDirectory); + ProjectJson = GetProjectJson(ProjectDirectory); + } + + public ProjectRootElement MSBuildProject { get; } + public JObject ProjectJson { get; } + public string ProjectDirectory { get; } + + public string ProjectJsonPath => Path.Combine(ProjectDirectory, "project.json"); + public string MSBuildProjectPath => Path.Combine(ProjectDirectory, s_temporaryDotnetNewMSBuildProjectName); + + public void Clean() + { + Directory.Delete(ProjectDirectory, true); + } + + private string CreateDotnetNewMSBuild(string projectName) + { + var guid = Guid.NewGuid().ToString(); + var tempDir = Path.Combine( + Path.GetTempPath(), + this.GetType().Namespace, + guid, + s_temporaryDotnetNewMSBuildProjectName); + + if (Directory.Exists(tempDir)) + { + Directory.Delete(tempDir, true); + } + Directory.CreateDirectory(tempDir); + + RunCommand("new", new string[] { "-t", "msbuild" }, tempDir); + + return tempDir; + } + + private ProjectRootElement GetMSBuildProject(string temporaryDotnetNewMSBuildDirectory) + { + var templateProjPath = Path.Combine(temporaryDotnetNewMSBuildDirectory, + s_temporaryDotnetNewMSBuildProjectName + ".csproj"); + + return ProjectRootElement.Open(templateProjPath); + } + + private JObject GetProjectJson(string temporaryDotnetNewMSBuildDirectory) + { + var projectJsonFile = Path.Combine(temporaryDotnetNewMSBuildDirectory, "project.json"); + return JObject.Parse(File.ReadAllText(projectJsonFile)); + } + + private void RunCommand(string commandToExecute, IEnumerable args, string workingDirectory) + { + var command = new DotNetCommandFactory() + .Create(commandToExecute, args) + .WorkingDirectory(workingDirectory) + .CaptureStdOut() + .CaptureStdErr(); + + var commandResult = command.Execute(); + + if (commandResult.ExitCode != 0) + { + throw new Exception($"Failed to run {commandToExecute} in directory: {workingDirectory}"); + } + } + } +} diff --git a/src/dotnet/project.json b/src/dotnet/project.json index 18f4ae78c..64590c7a4 100644 --- a/src/dotnet/project.json +++ b/src/dotnet/project.json @@ -45,6 +45,9 @@ "Microsoft.DotNet.Configurer": { "target": "project" }, + "Microsoft.DotNet.ProjectJsonMigration": { + "target": "project" + }, "Microsoft.DotNet.Tools.Test": { "target": "project" }, From d8556d1ed79a716376e058f432bbbdd921cce235 Mon Sep 17 00:00:00 2001 From: Bryan Thornbury Date: Mon, 22 Aug 2016 12:22:37 -0700 Subject: [PATCH 3/8] Include context fixes to include the default files even when buildoptions is empty, so we have less custom logic in the migration library --- .../Files/IncludeContext.cs | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/src/Microsoft.DotNet.ProjectModel/Files/IncludeContext.cs b/src/Microsoft.DotNet.ProjectModel/Files/IncludeContext.cs index ebedcdad3..bef8c47c7 100644 --- a/src/Microsoft.DotNet.ProjectModel/Files/IncludeContext.cs +++ b/src/Microsoft.DotNet.ProjectModel/Files/IncludeContext.cs @@ -38,7 +38,15 @@ namespace Microsoft.DotNet.ProjectModel.Files SourceBasePath = sourceBasePath; Option = option; var token = rawObject.Value(option); - if (token.Type != JTokenType.Object) + + if (token == null) + { + IncludePatterns = new List(); + ExcludePatterns = new List(); + IncludeFiles = new List(); + ExcludeFiles = new List(); + } + else if (token.Type != JTokenType.Object) { IncludePatterns = CreateCollection( sourceBasePath, option, ExtractValues(token), literalPath: false); From b900f0b32798b44c03ba3f7d23e3b75b2351fa1a Mon Sep 17 00:00:00 2001 From: Bryan Thornbury Date: Mon, 22 Aug 2016 12:23:32 -0700 Subject: [PATCH 4/8] Solution and project cleanup --- Microsoft.DotNet.Cli.sln | 74 ++++++++++++++++++- .../TestAppWithLibrary/TestApp/TestApp.xproj | 20 ----- .../TestLibrary/TestLibrary.xproj | 19 ----- ...Microsoft.DotNet.ProjectModel.Loader.xproj | 2 +- 4 files changed, 73 insertions(+), 42 deletions(-) delete mode 100644 TestAssets/TestProjects/TestAppWithLibrary/TestApp/TestApp.xproj delete mode 100644 TestAssets/TestProjects/TestAppWithLibrary/TestLibrary/TestLibrary.xproj diff --git a/Microsoft.DotNet.Cli.sln b/Microsoft.DotNet.Cli.sln index f8d3bd28e..6adc5857c 100644 --- a/Microsoft.DotNet.Cli.sln +++ b/Microsoft.DotNet.Cli.sln @@ -26,6 +26,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "test", "test", "{17735A9D-B EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tools", "tools", "{0722D325-24C8-4E83-B5AF-0A083E7F0749}" EndProject +Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "MultiProjectValidator", "tools\MultiProjectValidator\MultiProjectValidator.xproj", "{08A68C6A-86F6-4ED2-89A7-B166D33E9F85}" +EndProject Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "EndToEnd", "test\EndToEnd\EndToEnd.xproj", "{65741CB1-8AEE-4C66-8198-10A7EA0E4258}" EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "TestProjects", "TestProjects", "{713CBFBB-5392-438D-B766-A9A585EF1BB8}" @@ -102,6 +104,8 @@ Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "update-dependencies", "buil EndProject Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "Microsoft.DotNet.ProjectModel.Loader", "src\Microsoft.DotNet.ProjectModel.Loader\Microsoft.DotNet.ProjectModel.Loader.xproj", "{1C599FFD-FB52-4279-A8E5-465D3EC499E1}" EndProject +Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "Microsoft.DotNet.ProjectModel.Loader.Tests", "test\Microsoft.DotNet.ProjectModel.Loader.Tests\Microsoft.DotNet.ProjectModel.Loader.Tests.xproj", "{5DF6C9DA-6909-4EC0-909E-6913580BB4A4}" +EndProject Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "Microsoft.DotNet.Configurer", "src\Microsoft.DotNet.Configurer\Microsoft.DotNet.Configurer.xproj", "{E5ED47EF-BF25-4DA9-A7FE-290C642CBF0F}" EndProject Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "Microsoft.DotNet.Configurer.UnitTests", "test\Microsoft.DotNet.Configurer.UnitTests\Microsoft.DotNet.Configurer.UnitTests.xproj", "{4C3B06D5-B6D5-4E5B-A44F-3EBE52A1C759}" @@ -152,8 +156,6 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "publish", "publish", "{27B1 build\publish\PublishContent.targets = build\publish\PublishContent.targets EndProjectSection EndProject -Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "Microsoft.DotNet.Tools.Test", "src\Microsoft.DotNet.Tools.Test\Microsoft.DotNet.Tools.Test.xproj", "{6D028154-5518-4A56-BAD6-938A90E5BCF6}" -EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -246,6 +248,22 @@ Global {DCDFE282-03DE-4DBC-B90C-CC3CE3EC8162}.RelWithDebInfo|Any CPU.Build.0 = Release|Any CPU {DCDFE282-03DE-4DBC-B90C-CC3CE3EC8162}.RelWithDebInfo|x64.ActiveCfg = Release|Any CPU {DCDFE282-03DE-4DBC-B90C-CC3CE3EC8162}.RelWithDebInfo|x64.Build.0 = Release|Any CPU + {688870C8-9843-4F9E-8576-D39290AD0F25}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {688870C8-9843-4F9E-8576-D39290AD0F25}.Debug|Any CPU.Build.0 = Debug|Any CPU + {688870C8-9843-4F9E-8576-D39290AD0F25}.Debug|x64.ActiveCfg = Debug|Any CPU + {688870C8-9843-4F9E-8576-D39290AD0F25}.Debug|x64.Build.0 = Debug|Any CPU + {688870C8-9843-4F9E-8576-D39290AD0F25}.MinSizeRel|Any CPU.ActiveCfg = Debug|Any CPU + {688870C8-9843-4F9E-8576-D39290AD0F25}.MinSizeRel|Any CPU.Build.0 = Debug|Any CPU + {688870C8-9843-4F9E-8576-D39290AD0F25}.MinSizeRel|x64.ActiveCfg = Debug|Any CPU + {688870C8-9843-4F9E-8576-D39290AD0F25}.MinSizeRel|x64.Build.0 = Debug|Any CPU + {688870C8-9843-4F9E-8576-D39290AD0F25}.Release|Any CPU.ActiveCfg = Release|Any CPU + {688870C8-9843-4F9E-8576-D39290AD0F25}.Release|Any CPU.Build.0 = Release|Any CPU + {688870C8-9843-4F9E-8576-D39290AD0F25}.Release|x64.ActiveCfg = Release|Any CPU + {688870C8-9843-4F9E-8576-D39290AD0F25}.Release|x64.Build.0 = Release|Any CPU + {688870C8-9843-4F9E-8576-D39290AD0F25}.RelWithDebInfo|Any CPU.ActiveCfg = Release|Any CPU + {688870C8-9843-4F9E-8576-D39290AD0F25}.RelWithDebInfo|Any CPU.Build.0 = Release|Any CPU + {688870C8-9843-4F9E-8576-D39290AD0F25}.RelWithDebInfo|x64.ActiveCfg = Release|Any CPU + {688870C8-9843-4F9E-8576-D39290AD0F25}.RelWithDebInfo|x64.Build.0 = Release|Any CPU {65741CB1-8AEE-4C66-8198-10A7EA0E4258}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {65741CB1-8AEE-4C66-8198-10A7EA0E4258}.Debug|Any CPU.Build.0 = Debug|Any CPU {65741CB1-8AEE-4C66-8198-10A7EA0E4258}.Debug|x64.ActiveCfg = Debug|Any CPU @@ -838,6 +856,55 @@ Global {E4F46EAB-B5A5-4E60-9B9D-40A1FADBF45C}.RelWithDebInfo|Any CPU.Build.0 = Release|Any CPU {E4F46EAB-B5A5-4E60-9B9D-40A1FADBF45C}.RelWithDebInfo|x64.ActiveCfg = Release|Any CPU {E4F46EAB-B5A5-4E60-9B9D-40A1FADBF45C}.RelWithDebInfo|x64.Build.0 = Release|Any CPU + + {EDD6C92D-0A58-4FCB-A0E9-9D0FFC045177}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {EDD6C92D-0A58-4FCB-A0E9-9D0FFC045177}.Debug|Any CPU.Build.0 = Debug|Any CPU + {EDD6C92D-0A58-4FCB-A0E9-9D0FFC045177}.Debug|x64.ActiveCfg = Debug|Any CPU + {EDD6C92D-0A58-4FCB-A0E9-9D0FFC045177}.Debug|x64.Build.0 = Debug|Any CPU + {EDD6C92D-0A58-4FCB-A0E9-9D0FFC045177}.MinSizeRel|Any CPU.ActiveCfg = Debug|Any CPU + {EDD6C92D-0A58-4FCB-A0E9-9D0FFC045177}.MinSizeRel|Any CPU.Build.0 = Debug|Any CPU + {EDD6C92D-0A58-4FCB-A0E9-9D0FFC045177}.MinSizeRel|x64.ActiveCfg = Debug|Any CPU + {EDD6C92D-0A58-4FCB-A0E9-9D0FFC045177}.MinSizeRel|x64.Build.0 = Debug|Any CPU + {EDD6C92D-0A58-4FCB-A0E9-9D0FFC045177}.Release|Any CPU.ActiveCfg = Release|Any CPU + {EDD6C92D-0A58-4FCB-A0E9-9D0FFC045177}.Release|Any CPU.Build.0 = Release|Any CPU + {EDD6C92D-0A58-4FCB-A0E9-9D0FFC045177}.Release|x64.ActiveCfg = Release|Any CPU + {EDD6C92D-0A58-4FCB-A0E9-9D0FFC045177}.Release|x64.Build.0 = Release|Any CPU + {EDD6C92D-0A58-4FCB-A0E9-9D0FFC045177}.RelWithDebInfo|Any CPU.ActiveCfg = Release|Any CPU + {EDD6C92D-0A58-4FCB-A0E9-9D0FFC045177}.RelWithDebInfo|Any CPU.Build.0 = Release|Any CPU + {EDD6C92D-0A58-4FCB-A0E9-9D0FFC045177}.RelWithDebInfo|x64.ActiveCfg = Release|Any CPU + {EDD6C92D-0A58-4FCB-A0E9-9D0FFC045177}.RelWithDebInfo|x64.Build.0 = Release|Any CPU + {0E083818-2320-4388-8007-4F720FD5C634}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {0E083818-2320-4388-8007-4F720FD5C634}.Debug|Any CPU.Build.0 = Debug|Any CPU + {0E083818-2320-4388-8007-4F720FD5C634}.Debug|x64.ActiveCfg = Debug|x64 + {0E083818-2320-4388-8007-4F720FD5C634}.Debug|x64.Build.0 = Debug|x64 + {0E083818-2320-4388-8007-4F720FD5C634}.MinSizeRel|Any CPU.ActiveCfg = MinSizeRel|Any CPU + {0E083818-2320-4388-8007-4F720FD5C634}.MinSizeRel|Any CPU.Build.0 = MinSizeRel|Any CPU + {0E083818-2320-4388-8007-4F720FD5C634}.MinSizeRel|x64.ActiveCfg = MinSizeRel|x64 + {0E083818-2320-4388-8007-4F720FD5C634}.MinSizeRel|x64.Build.0 = MinSizeRel|x64 + {0E083818-2320-4388-8007-4F720FD5C634}.Release|Any CPU.ActiveCfg = Release|Any CPU + {0E083818-2320-4388-8007-4F720FD5C634}.Release|Any CPU.Build.0 = Release|Any CPU + {0E083818-2320-4388-8007-4F720FD5C634}.Release|x64.ActiveCfg = Release|x64 + {0E083818-2320-4388-8007-4F720FD5C634}.Release|x64.Build.0 = Release|x64 + {0E083818-2320-4388-8007-4F720FD5C634}.RelWithDebInfo|Any CPU.ActiveCfg = RelWithDebInfo|Any CPU + {0E083818-2320-4388-8007-4F720FD5C634}.RelWithDebInfo|Any CPU.Build.0 = RelWithDebInfo|Any CPU + {0E083818-2320-4388-8007-4F720FD5C634}.RelWithDebInfo|x64.ActiveCfg = RelWithDebInfo|x64 + {0E083818-2320-4388-8007-4F720FD5C634}.RelWithDebInfo|x64.Build.0 = RelWithDebInfo|x64 + {1F2EF070-AC5F-4078-AFB0-65745AC691B9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {1F2EF070-AC5F-4078-AFB0-65745AC691B9}.Debug|Any CPU.Build.0 = Debug|Any CPU + {1F2EF070-AC5F-4078-AFB0-65745AC691B9}.Debug|x64.ActiveCfg = Debug|x64 + {1F2EF070-AC5F-4078-AFB0-65745AC691B9}.Debug|x64.Build.0 = Debug|x64 + {1F2EF070-AC5F-4078-AFB0-65745AC691B9}.MinSizeRel|Any CPU.ActiveCfg = MinSizeRel|Any CPU + {1F2EF070-AC5F-4078-AFB0-65745AC691B9}.MinSizeRel|Any CPU.Build.0 = MinSizeRel|Any CPU + {1F2EF070-AC5F-4078-AFB0-65745AC691B9}.MinSizeRel|x64.ActiveCfg = MinSizeRel|x64 + {1F2EF070-AC5F-4078-AFB0-65745AC691B9}.MinSizeRel|x64.Build.0 = MinSizeRel|x64 + {1F2EF070-AC5F-4078-AFB0-65745AC691B9}.Release|Any CPU.ActiveCfg = Release|Any CPU + {1F2EF070-AC5F-4078-AFB0-65745AC691B9}.Release|Any CPU.Build.0 = Release|Any CPU + {1F2EF070-AC5F-4078-AFB0-65745AC691B9}.Release|x64.ActiveCfg = Release|x64 + {1F2EF070-AC5F-4078-AFB0-65745AC691B9}.Release|x64.Build.0 = Release|x64 + {1F2EF070-AC5F-4078-AFB0-65745AC691B9}.RelWithDebInfo|Any CPU.ActiveCfg = RelWithDebInfo|Any CPU + {1F2EF070-AC5F-4078-AFB0-65745AC691B9}.RelWithDebInfo|Any CPU.Build.0 = RelWithDebInfo|Any CPU + {1F2EF070-AC5F-4078-AFB0-65745AC691B9}.RelWithDebInfo|x64.ActiveCfg = RelWithDebInfo|x64 + {1F2EF070-AC5F-4078-AFB0-65745AC691B9}.RelWithDebInfo|x64.Build.0 = RelWithDebInfo|x64 {6D028154-5518-4A56-BAD6-938A90E5BCF6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {6D028154-5518-4A56-BAD6-938A90E5BCF6}.Debug|Any CPU.Build.0 = Debug|Any CPU {6D028154-5518-4A56-BAD6-938A90E5BCF6}.Debug|x64.ActiveCfg = Debug|x64 @@ -911,5 +978,8 @@ Global {E4F46EAB-B5A5-4E60-9B9D-40A1FADBF45C} = {17735A9D-BFD9-4585-A7CB-3208CA6EA8A7} {27B12960-ABB0-4903-9C60-5E9157E659C8} = {89905EC4-BC0F-443B-8ADF-691321F10108} {6D028154-5518-4A56-BAD6-938A90E5BCF6} = {ED2FE3E2-F7E7-4389-8231-B65123F2076F} + {EDD6C92D-0A58-4FCB-A0E9-9D0FFC045177} = {17735A9D-BFD9-4585-A7CB-3208CA6EA8A7} + {0E083818-2320-4388-8007-4F720FD5C634} = {ED2FE3E2-F7E7-4389-8231-B65123F2076F} + {1F2EF070-AC5F-4078-AFB0-65745AC691B9} = {17735A9D-BFD9-4585-A7CB-3208CA6EA8A7} EndGlobalSection EndGlobal diff --git a/TestAssets/TestProjects/TestAppWithLibrary/TestApp/TestApp.xproj b/TestAssets/TestProjects/TestAppWithLibrary/TestApp/TestApp.xproj deleted file mode 100644 index 4cef17daa..000000000 --- a/TestAssets/TestProjects/TestAppWithLibrary/TestApp/TestApp.xproj +++ /dev/null @@ -1,20 +0,0 @@ - - - - 14.0 - $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) - - - - - 58808bbc-371e-47d6-a3d0-4902145eda4e - TestApp - ..\..\artifacts\obj\$(MSBuildProjectName) - ..\..\artifacts\bin\$(MSBuildProjectName)\ - - - - 2.0 - - - diff --git a/TestAssets/TestProjects/TestAppWithLibrary/TestLibrary/TestLibrary.xproj b/TestAssets/TestProjects/TestAppWithLibrary/TestLibrary/TestLibrary.xproj deleted file mode 100644 index eb9f8bc2d..000000000 --- a/TestAssets/TestProjects/TestAppWithLibrary/TestLibrary/TestLibrary.xproj +++ /dev/null @@ -1,19 +0,0 @@ - - - - 14.0 - $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) - - - - 947dd232-8d9b-4b78-9c6a-94f807d2dd58 - TestLibrary - ..\..\artifacts\obj\$(MSBuildProjectName) - ..\..\artifacts\bin\$(MSBuildProjectName)\ - - - - 2.0 - - - \ No newline at end of file diff --git a/src/Microsoft.DotNet.ProjectModel.Loader/Microsoft.DotNet.ProjectModel.Loader.xproj b/src/Microsoft.DotNet.ProjectModel.Loader/Microsoft.DotNet.ProjectModel.Loader.xproj index 95b2a8b7f..bb7e99e2f 100644 --- a/src/Microsoft.DotNet.ProjectModel.Loader/Microsoft.DotNet.ProjectModel.Loader.xproj +++ b/src/Microsoft.DotNet.ProjectModel.Loader/Microsoft.DotNet.ProjectModel.Loader.xproj @@ -6,7 +6,7 @@ - 1c599ffd-fb52-4279-a8e5-465d3ec499e1 + 651DDD55-4819-4F20-B619-4F8A5BC2DF6D Microsoft.DotNet.ProjectModel.Loader ..\..\artifacts\obj\$(MSBuildProjectName) ..\..\artifacts\bin From 47cf8fbda6134ab9837234faeb12e51ccfa968d5 Mon Sep 17 00:00:00 2001 From: Bryan Thornbury Date: Mon, 22 Aug 2016 12:23:54 -0700 Subject: [PATCH 5/8] Additional TestAssets --- .../TestAppWithMigrateAbleScripts/Program.cs | 17 +++++++++++ .../echoscript.cmd | 2 ++ .../echoscript.sh | 2 ++ .../project.json | 29 +++++++++++++++++++ .../TestAppWithRuntimeOptions/Program.cs | 12 ++++++++ .../TestAppWithRuntimeOptions/project.json | 26 +++++++++++++++++ 6 files changed, 88 insertions(+) create mode 100644 TestAssets/TestProjects/TestAppWithMigrateAbleScripts/Program.cs create mode 100644 TestAssets/TestProjects/TestAppWithMigrateAbleScripts/echoscript.cmd create mode 100755 TestAssets/TestProjects/TestAppWithMigrateAbleScripts/echoscript.sh create mode 100644 TestAssets/TestProjects/TestAppWithMigrateAbleScripts/project.json create mode 100644 TestAssets/TestProjects/TestAppWithRuntimeOptions/Program.cs create mode 100644 TestAssets/TestProjects/TestAppWithRuntimeOptions/project.json diff --git a/TestAssets/TestProjects/TestAppWithMigrateAbleScripts/Program.cs b/TestAssets/TestProjects/TestAppWithMigrateAbleScripts/Program.cs new file mode 100644 index 000000000..7bcd4fbd3 --- /dev/null +++ b/TestAssets/TestProjects/TestAppWithMigrateAbleScripts/Program.cs @@ -0,0 +1,17 @@ +// Copyright (c) .NET Foundation and contributors. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System; +using System.Diagnostics; + +namespace TestApp +{ + public class Program + { + public static int Main(string[] args) + { + Console.WriteLine("Hello World"); + return 0; + } + } +} diff --git a/TestAssets/TestProjects/TestAppWithMigrateAbleScripts/echoscript.cmd b/TestAssets/TestProjects/TestAppWithMigrateAbleScripts/echoscript.cmd new file mode 100644 index 000000000..a996a370f --- /dev/null +++ b/TestAssets/TestProjects/TestAppWithMigrateAbleScripts/echoscript.cmd @@ -0,0 +1,2 @@ +@echo off +echo %* \ No newline at end of file diff --git a/TestAssets/TestProjects/TestAppWithMigrateAbleScripts/echoscript.sh b/TestAssets/TestProjects/TestAppWithMigrateAbleScripts/echoscript.sh new file mode 100755 index 000000000..778a035de --- /dev/null +++ b/TestAssets/TestProjects/TestAppWithMigrateAbleScripts/echoscript.sh @@ -0,0 +1,2 @@ +#!/usr/bin/env sh +echo $@ \ No newline at end of file diff --git a/TestAssets/TestProjects/TestAppWithMigrateAbleScripts/project.json b/TestAssets/TestProjects/TestAppWithMigrateAbleScripts/project.json new file mode 100644 index 000000000..aaad2e215 --- /dev/null +++ b/TestAssets/TestProjects/TestAppWithMigrateAbleScripts/project.json @@ -0,0 +1,29 @@ +{ + "version": "1.0.0-*", + "buildOptions": { + "emitEntryPoint": true + }, + "dependencies": { + "Microsoft.NETCore.App": { + "type": "platform", + "version": "1.0.0", + } + }, + "frameworks": { + "netcoreapp1.0": {} + }, + "scripts": { + "prepublish": [ + "echoscript prepublish_output ?%publish:ProjectPath%? ?%publish:Configuration%? ?%publish:OutputPath%? ?%publish:FullTargetFramework%?" + ], + "postpublish": [ + "echoscript postpublish_output ?%publish:ProjectPath%? ?%publish:Configuration%? ?%publish:OutputPath%? ?%publish:FullTargetFramework%?" + ], + "precompile": [ + "echoscript precompile_output ?%compile:Configuration%? ?%compile:OutputDir%? ?%compile:FullTargetFramework%?" + ], + "postcompile": [ + "echoscript postcompile_output ?%compile:Configuration%? ?%compile:OutputDir%? ?%compile:FullTargetFramework%?" + ] + } +} diff --git a/TestAssets/TestProjects/TestAppWithRuntimeOptions/Program.cs b/TestAssets/TestProjects/TestAppWithRuntimeOptions/Program.cs new file mode 100644 index 000000000..51233cffa --- /dev/null +++ b/TestAssets/TestProjects/TestAppWithRuntimeOptions/Program.cs @@ -0,0 +1,12 @@ +using System; + +namespace ConsoleApplication +{ + public class Program + { + public static void Main(string[] args) + { + Console.WriteLine("Hello World!"); + } + } +} diff --git a/TestAssets/TestProjects/TestAppWithRuntimeOptions/project.json b/TestAssets/TestProjects/TestAppWithRuntimeOptions/project.json new file mode 100644 index 000000000..822e90d6a --- /dev/null +++ b/TestAssets/TestProjects/TestAppWithRuntimeOptions/project.json @@ -0,0 +1,26 @@ +{ + "version": "1.0.0-*", + "buildOptions": { + "emitEntryPoint": true + }, + "dependencies": { + "Microsoft.NETCore.App": { + "version": "1.0.0", + "type": "platform" + } + }, + "frameworks": { + "netcoreapp1.0": {} + }, + "runtimeOptions": { + "somethingString": "anything", + "somethingBoolean": true, + "someArray": [ + "one", + "two" + ], + "someObject": { + "someProperty": "someValue" + } + } +} From 28e2aa493dea700ba2a13f3c6d61102d349de0b7 Mon Sep 17 00:00:00 2001 From: Bryan Thornbury Date: Mon, 22 Aug 2016 12:24:10 -0700 Subject: [PATCH 6/8] ProjectJsonMigration Tests --- .../GivenMSBuildExtensions.cs | 109 +++++ .../MSBuild.exe | 0 ...ft.DotNet.ProjectJsonMigration.Tests.xproj | 21 + .../ProjectJsonBuilder.cs | 117 +++++ .../GivenThatIWantToMigrateBuildOptions.cs | 449 ++++++++++++++++++ .../GivenThatIWantToMigrateConfigurations.cs | 162 +++++++ ...enThatIWantToMigrateProjectDependencies.cs | 85 ++++ .../GivenThatIWantToMigratePublishOptions.cs | 149 ++++++ .../GivenThatIWantToMigrateRuntimeOptions.cs | 66 +++ .../Rules/GivenThatIWantToMigrateScripts.cs | 205 ++++++++ .../Rules/GivenThatIWantToMigrateTFMs.cs | 43 ++ .../GivenThatIWantToMigrateTestApps.cs | 212 +++++++++ .../TemporaryProjectFileRuleRunner.cs | 43 ++ .../Transforms/GivenAConditionalTransform.cs | 44 ++ .../Transforms/GivenATransformApplicator.cs | 89 ++++ .../GivenAnAddBoolPropertyTransform.cs | 39 ++ .../Transforms/GivenAnAddItemTransform.cs | 84 ++++ .../Transforms/GivenAnAddPropertyTransform.cs | 93 ++++ .../GivenAnAddStringPropertyTransform.cs | 57 +++ .../GivenAnIncludeContextTransformation.cs | 11 + .../msbuild.exe.config | 1 + .../project.json | 39 ++ 22 files changed, 2118 insertions(+) create mode 100644 test/Microsoft.DotNet.ProjectJsonMigration.Tests/GivenMSBuildExtensions.cs create mode 100644 test/Microsoft.DotNet.ProjectJsonMigration.Tests/MSBuild.exe create mode 100644 test/Microsoft.DotNet.ProjectJsonMigration.Tests/Microsoft.DotNet.ProjectJsonMigration.Tests.xproj create mode 100644 test/Microsoft.DotNet.ProjectJsonMigration.Tests/ProjectJsonBuilder.cs create mode 100644 test/Microsoft.DotNet.ProjectJsonMigration.Tests/Rules/GivenThatIWantToMigrateBuildOptions.cs create mode 100644 test/Microsoft.DotNet.ProjectJsonMigration.Tests/Rules/GivenThatIWantToMigrateConfigurations.cs create mode 100644 test/Microsoft.DotNet.ProjectJsonMigration.Tests/Rules/GivenThatIWantToMigrateProjectDependencies.cs create mode 100644 test/Microsoft.DotNet.ProjectJsonMigration.Tests/Rules/GivenThatIWantToMigratePublishOptions.cs create mode 100644 test/Microsoft.DotNet.ProjectJsonMigration.Tests/Rules/GivenThatIWantToMigrateRuntimeOptions.cs create mode 100644 test/Microsoft.DotNet.ProjectJsonMigration.Tests/Rules/GivenThatIWantToMigrateScripts.cs create mode 100644 test/Microsoft.DotNet.ProjectJsonMigration.Tests/Rules/GivenThatIWantToMigrateTFMs.cs create mode 100644 test/Microsoft.DotNet.ProjectJsonMigration.Tests/Scenarios/GivenThatIWantToMigrateTestApps.cs create mode 100644 test/Microsoft.DotNet.ProjectJsonMigration.Tests/TemporaryProjectFileRuleRunner.cs create mode 100644 test/Microsoft.DotNet.ProjectJsonMigration.Tests/Transforms/GivenAConditionalTransform.cs create mode 100644 test/Microsoft.DotNet.ProjectJsonMigration.Tests/Transforms/GivenATransformApplicator.cs create mode 100644 test/Microsoft.DotNet.ProjectJsonMigration.Tests/Transforms/GivenAnAddBoolPropertyTransform.cs create mode 100644 test/Microsoft.DotNet.ProjectJsonMigration.Tests/Transforms/GivenAnAddItemTransform.cs create mode 100644 test/Microsoft.DotNet.ProjectJsonMigration.Tests/Transforms/GivenAnAddPropertyTransform.cs create mode 100644 test/Microsoft.DotNet.ProjectJsonMigration.Tests/Transforms/GivenAnAddStringPropertyTransform.cs create mode 100644 test/Microsoft.DotNet.ProjectJsonMigration.Tests/Transforms/GivenAnIncludeContextTransformation.cs create mode 100644 test/Microsoft.DotNet.ProjectJsonMigration.Tests/msbuild.exe.config create mode 100644 test/Microsoft.DotNet.ProjectJsonMigration.Tests/project.json diff --git a/test/Microsoft.DotNet.ProjectJsonMigration.Tests/GivenMSBuildExtensions.cs b/test/Microsoft.DotNet.ProjectJsonMigration.Tests/GivenMSBuildExtensions.cs new file mode 100644 index 000000000..bf2923a10 --- /dev/null +++ b/test/Microsoft.DotNet.ProjectJsonMigration.Tests/GivenMSBuildExtensions.cs @@ -0,0 +1,109 @@ +using System.Linq; +using FluentAssertions; +using Microsoft.Build.Construction; +using Xunit; + +namespace Microsoft.DotNet.ProjectJsonMigration.Tests +{ + public class GivenMSBuildExtensions + { + [Fact] + public void Includes_returns_include_value_split_by_semicolon() + { + var project = ProjectRootElement.Create(); + var item = project.CreateItemElement("test"); + item.Include = "include1;include2;aaa"; + + var includes = item.Includes().ToArray(); + includes[0].Should().Be("include1"); + includes[1].Should().Be("include2"); + includes[2].Should().Be("aaa"); + } + + [Fact] + public void Excludes_returns_include_value_split_by_semicolon() + { + var project = ProjectRootElement.Create(); + var item = project.CreateItemElement("test"); + item.Exclude = "include1;include2;aaa"; + + var excludes = item.Excludes().ToArray(); + excludes[0].Should().Be("include1"); + excludes[1].Should().Be("include2"); + excludes[2].Should().Be("aaa"); + } + + [Fact] + public void ItemsWithoutConditions_returns_items_without_a_condition() + { + var project = ProjectRootElement.Create(); + var item = project.AddItem("test", "include1"); + + project.ItemsWithoutConditions().Count().Should().Be(1); + project.ItemsWithoutConditions().First().Should().Be(item); + } + + [Fact] + public void ItemsWithoutConditions_doesnt_return_items_with_a_condition() + { + var project = ProjectRootElement.Create(); + var conditionlessItems = project.AddItem("test", "include1"); + var conditionItem = project.AddItem("test2", "include2"); + conditionItem.Condition = "SomeCondition"; + + project.ItemsWithoutConditions().Count().Should().Be(1); + project.ItemsWithoutConditions().First().Should().Be(conditionlessItems); + } + + [Fact] + public void ItemsWithoutConditions_doesnt_return_items_with_a_parent_with_a_condition() + { + var project = ProjectRootElement.Create(); + var conditionlessItems = project.AddItem("test", "include1"); + + var conditionItemGroup = project.AddItemGroup(); + conditionItemGroup.Condition = "SomeCondition"; + conditionItemGroup.AddItem("test2", "include2"); + + project.ItemsWithoutConditions().Count().Should().Be(1); + project.ItemsWithoutConditions().First().Should().Be(conditionlessItems); + } + + [Fact] + public void AddIncludes_merges_include_sets() + { + var project = ProjectRootElement.Create(); + var item1 = project.AddItem("test", "include1;include2"); + item1.AddIncludes(new string[] {"include2", "include3"}); + + item1.Include.Should().Be("include1;include2;include3"); + } + + [Fact] + public void AddExcludes_merges_include_sets() + { + var project = ProjectRootElement.Create(); + var item1 = project.AddItem("test", "include1"); + item1.Exclude = "exclude1;exclude2"; + item1.AddExcludes(new string[] {"exclude2", "exclude3"}); + + item1.Exclude.Should().Be("exclude1;exclude2;exclude3"); + } + + [Fact] + public void AddMetadata_adds_metadata_available_via_Metadata_on_an_item() + { + var project = ProjectRootElement.Create(); + var item1 = project.AddItem("test", "include1"); + item1.AddMetadata("name", "value"); + item1.HasMetadata.Should().BeTrue(); + + var item2 = project.AddItem("test1", "hey"); + item2.AddMetadata(item1.Metadata); + + item2.HasMetadata.Should().BeTrue(); + item2.Metadata.First().Name.Should().Be("name"); + item2.Metadata.First().Value.Should().Be("value"); + } + } +} \ No newline at end of file diff --git a/test/Microsoft.DotNet.ProjectJsonMigration.Tests/MSBuild.exe b/test/Microsoft.DotNet.ProjectJsonMigration.Tests/MSBuild.exe new file mode 100644 index 000000000..e69de29bb diff --git a/test/Microsoft.DotNet.ProjectJsonMigration.Tests/Microsoft.DotNet.ProjectJsonMigration.Tests.xproj b/test/Microsoft.DotNet.ProjectJsonMigration.Tests/Microsoft.DotNet.ProjectJsonMigration.Tests.xproj new file mode 100644 index 000000000..04e81394b --- /dev/null +++ b/test/Microsoft.DotNet.ProjectJsonMigration.Tests/Microsoft.DotNet.ProjectJsonMigration.Tests.xproj @@ -0,0 +1,21 @@ + + + + 14.0.25123 + $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) + + + + 1F2EF070-AC5F-4078-AFB0-65745AC691B9 + Microsoft.DotNet.ProjectJsonMigration.Tests + .\obj + .\bin\ + + + 2.0 + + + + + + \ No newline at end of file diff --git a/test/Microsoft.DotNet.ProjectJsonMigration.Tests/ProjectJsonBuilder.cs b/test/Microsoft.DotNet.ProjectJsonMigration.Tests/ProjectJsonBuilder.cs new file mode 100644 index 000000000..204aa2afc --- /dev/null +++ b/test/Microsoft.DotNet.ProjectJsonMigration.Tests/ProjectJsonBuilder.cs @@ -0,0 +1,117 @@ +using Microsoft.DotNet.TestFramework; +using Newtonsoft.Json.Linq; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Threading.Tasks; + +namespace Microsoft.DotNet.ProjectJsonMigration.Tests +{ + /// + /// Used to build up test scenario project.jsons without needing to add a new test asset. + /// + public class ProjectJsonBuilder + { + private static readonly string s_defaultProjectJsonTestAsset = "TestAppWithRuntimeOptions"; + + private TestAssetsManager _testAssetsManager; + private JObject _projectJson; + + private bool _baseDefined = false; + private bool _baseProjectDirectory; + + public ProjectJsonBuilder(TestAssetsManager testAssetsManager) + { + _testAssetsManager = testAssetsManager; + } + + public string SaveToDisk(string outputDirectory) + { + EnsureBaseIsSet(); + + var projectPath = Path.Combine(outputDirectory, "project.json"); + File.WriteAllText(projectPath, _projectJson.ToString()); + return projectPath; + } + + public JObject Build() + { + EnsureBaseIsSet(); + return _projectJson; + } + + public ProjectJsonBuilder FromDefaultBase() + { + return FromTestAssetBase(s_defaultProjectJsonTestAsset); + } + + public ProjectJsonBuilder FromTestAssetBase(string testAssetName) + { + var testProjectDirectory = _testAssetsManager.CreateTestInstance(testAssetName).Path; + var testProject = Path.Combine(testProjectDirectory, "project.json"); + + SetBase(JObject.Parse(File.ReadAllText(testProject))); + + return this; + } + + public ProjectJsonBuilder FromStringBase(string jsonString) + { + SetBase(JObject.Parse(jsonString)); + return this; + } + + public ProjectJsonBuilder FromEmptyBase() + { + SetBase(new JObject()); + return this; + } + + public ProjectJsonBuilder WithCustomProperty(string propertyName, Dictionary value) + { + EnsureBaseIsSet(); + + _projectJson[propertyName] = JObject.FromObject(value); + + return this; + } + + public ProjectJsonBuilder WithCustomProperty(string propertyName, string value) + { + EnsureBaseIsSet(); + + _projectJson[propertyName] = value; + + return this; + } + + public ProjectJsonBuilder WithCustomProperty(string propertyName, string[] value) + { + EnsureBaseIsSet(); + + _projectJson[propertyName] = JArray.FromObject(value); + + return this; + } + + private void SetBase(JObject project) + { + if (_baseDefined) + { + throw new Exception("Base was already defined."); + } + _baseDefined = true; + + _projectJson = project; + } + + private void EnsureBaseIsSet() + { + if (!_baseDefined) + { + throw new Exception("Cannot build without base set"); + } + } + } +} diff --git a/test/Microsoft.DotNet.ProjectJsonMigration.Tests/Rules/GivenThatIWantToMigrateBuildOptions.cs b/test/Microsoft.DotNet.ProjectJsonMigration.Tests/Rules/GivenThatIWantToMigrateBuildOptions.cs new file mode 100644 index 000000000..4408d9a8b --- /dev/null +++ b/test/Microsoft.DotNet.ProjectJsonMigration.Tests/Rules/GivenThatIWantToMigrateBuildOptions.cs @@ -0,0 +1,449 @@ +using Microsoft.Build.Construction; +using Microsoft.DotNet.ProjectJsonMigration; +using Microsoft.DotNet.ProjectModel; +using Microsoft.DotNet.Tools.Test.Utilities; +using NuGet.Frameworks; +using System; +using System.IO; +using System.Linq; +using Xunit; +using FluentAssertions; +using Microsoft.DotNet.ProjectModel.Files; + +namespace Microsoft.DotNet.ProjectJsonMigration.Tests +{ + public class GivenThatIWantToMigrateBuildOptions : TestBase + { + [Fact] + public void Specified_default_properties_are_removed_when_they_exists_in_the_csproj_template() + { + // Setup project with default properties + var defaultPropertiesExpectedToBeRemoved = new string[] + { + "OutputType", + "TargetExt" + }; + + var defaultValue = "defaultValue"; + + var templateProj = ProjectRootElement.Create(); + var defaultPropertyGroup = templateProj.AddPropertyGroup(); + + foreach (var defaultPropertyName in defaultPropertiesExpectedToBeRemoved) + { + defaultPropertyGroup.AddProperty(defaultPropertyName, defaultValue); + } + + // Setup projectcontext + var testProjectDirectory = TestAssetsManager.CreateTestInstance("TestAppWithRuntimeOptions").Path; + var projectContext = ProjectContext.Create(testProjectDirectory, FrameworkConstants.CommonFrameworks.NetCoreApp10); + + var testSettings = new MigrationSettings(testProjectDirectory, testProjectDirectory, "1.0.0", templateProj); + var testInputs = new MigrationRuleInputs(new[] {projectContext}, templateProj, templateProj.AddItemGroup(), + templateProj.AddPropertyGroup()); + new MigrateBuildOptionsRule().Apply(testSettings, testInputs); + + defaultPropertyGroup.Properties.Count.Should().Be(0); + } + + [Fact] + public void Migrating_empty_buildOptions_populates_only_AssemblyName_and_OutputType() + { + 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.Items.Count().Should().Be(2); + mockProj.Items.First(i => i.ItemType == "Compile").Include.Should().Be(@"**\*.cs"); + mockProj.Items.First(i => i.ItemType == "Compile").Exclude.Should().Be(@"bin\**;obj\**;**\*.xproj;packages\**"); + mockProj.Items.First(i => i.ItemType == "EmbeddedResource").Include.Should().Be(@"compiler\resources\**\*;**\*.resx"); + mockProj.Items.First(i => i.ItemType == "EmbeddedResource").Exclude.Should().Be(@"bin\**;obj\**;**\*.xproj;packages\**"); + } + + [Fact] + public void Migrating_EmitEntryPoint_true_populates_OutputType_and_TargetExt_fields() + { + var mockProj = RunBuildOptionsRuleOnPj(@" + { + ""buildOptions"": { + ""emitEntryPoint"": ""true"" + } + }"); + + mockProj.Properties.Count(p => p.Name == "OutputType").Should().Be(1); + mockProj.Properties.Count(p => p.Name == "TargetExt").Should().Be(1); + + mockProj.Properties.First(p => p.Name == "OutputType").Value.Should().Be("Exe"); + mockProj.Properties.First(p => p.Name == "TargetExt").Value.Should().Be(".dll"); + } + + [Fact] + public void Migrating_EmitEntryPoint_false_populates_OutputType_fields() + { + var mockProj = RunBuildOptionsRuleOnPj(@" + { + ""buildOptions"": { + ""emitEntryPoint"": ""false"" + } + }"); + + mockProj.Properties.Count(p => p.Name == "OutputType").Should().Be(1); + mockProj.Properties.First(p => p.Name == "OutputType").Value.Should().Be("Library"); + } + + [Fact] + public void Migrating_define_populates_DefineConstants() + { + var mockProj = RunBuildOptionsRuleOnPj(@" + { + ""buildOptions"": { + ""define"": [ ""DEBUG"", ""TRACE"" ] + } + }"); + + mockProj.Properties.Count(p => p.Name == "DefineConstants").Should().Be(1); + mockProj.Properties.First(p => p.Name == "DefineConstants").Value.Should().Be("DEBUG;TRACE"); + } + + [Fact] + public void Migrating_nowarn_populates_NoWarn() + { + var mockProj = RunBuildOptionsRuleOnPj(@" + { + ""buildOptions"": { + ""nowarn"": [ ""CS0168"", ""CS0219"" ] + } + }"); + + mockProj.Properties.Count(p => p.Name == "NoWarn").Should().Be(1); + mockProj.Properties.First(p => p.Name == "NoWarn").Value.Should().Be("CS0168;CS0219"); + } + + [Fact] + public void Migrating_warningsAsErrors_populates_WarningsAsErrors() + { + var mockProj = RunBuildOptionsRuleOnPj(@" + { + ""buildOptions"": { + ""warningsAsErrors"": ""true"" + } + }"); + + mockProj.Properties.Count(p => p.Name == "WarningsAsErrors").Should().Be(1); + mockProj.Properties.First(p => p.Name == "WarningsAsErrors").Value.Should().Be("true"); + + mockProj = RunBuildOptionsRuleOnPj(@" + { + ""buildOptions"": { + ""warningsAsErrors"": ""false"" + } + }"); + + mockProj.Properties.Count(p => p.Name == "WarningsAsErrors").Should().Be(0); + } + + [Fact] + public void Migrating_allowUnsafe_populates_AllowUnsafeBlocks() + { + var mockProj = RunBuildOptionsRuleOnPj(@" + { + ""buildOptions"": { + ""allowUnsafe"": ""true"" + } + }"); + + mockProj.Properties.Count(p => p.Name == "AllowUnsafeBlocks").Should().Be(1); + mockProj.Properties.First(p => p.Name == "AllowUnsafeBlocks").Value.Should().Be("true"); + + mockProj = RunBuildOptionsRuleOnPj(@" + { + ""buildOptions"": { + ""allowUnsafe"": ""false"" + } + }"); + + mockProj.Properties.Count(p => p.Name == "AllowUnsafeBlocks").Should().Be(0); + } + + [Fact] + public void Migrating_optimize_populates_Optimize() + { + var mockProj = RunBuildOptionsRuleOnPj(@" + { + ""buildOptions"": { + ""optimize"": ""true"" + } + }"); + + mockProj.Properties.Count(p => p.Name == "Optimize").Should().Be(1); + mockProj.Properties.First(p => p.Name == "Optimize").Value.Should().Be("true"); + + mockProj = RunBuildOptionsRuleOnPj(@" + { + ""buildOptions"": { + ""optimize"": ""false"" + } + }"); + + mockProj.Properties.Count(p => p.Name == "Optimize").Should().Be(0); + } + + [Fact] + public void Migrating_platform_populates_PlatformTarget() + { + var mockProj = RunBuildOptionsRuleOnPj(@" + { + ""buildOptions"": { + ""platform"": ""x64"" + } + }"); + + mockProj.Properties.Count(p => p.Name == "PlatformTarget").Should().Be(1); + mockProj.Properties.First(p => p.Name == "PlatformTarget").Value.Should().Be("x64"); + + mockProj = RunBuildOptionsRuleOnPj(@" + { + ""buildOptions"": { + ""platform"": ""x86"" + } + }"); + + mockProj.Properties.Count(p => p.Name == "PlatformTarget").Should().Be(1); + mockProj.Properties.First(p => p.Name == "PlatformTarget").Value.Should().Be("x86"); + + mockProj = RunBuildOptionsRuleOnPj(@" + { + ""buildOptions"": { + ""platform"": ""foo"" + } + }"); + + mockProj.Properties.Count(p => p.Name == "PlatformTarget").Should().Be(1); + mockProj.Properties.First(p => p.Name == "PlatformTarget").Value.Should().Be("foo"); + } + + [Fact] + public void Migrating_languageVersion_populates_LangVersion() + { + var mockProj = RunBuildOptionsRuleOnPj(@" + { + ""buildOptions"": { + ""languageVersion"": ""5"" + } + }"); + + mockProj.Properties.Count(p => p.Name == "LangVersion").Should().Be(1); + mockProj.Properties.First(p => p.Name == "LangVersion").Value.Should().Be("5"); + } + + [Fact] + public void Migrating_keyFile_populates_AssemblyOriginatorKeyFile_and_SignAssembly() + { + var mockProj = RunBuildOptionsRuleOnPj(@" + { + ""buildOptions"": { + ""keyFile"": ""../keyfile.snk"" + } + }"); + + mockProj.Properties.Count(p => p.Name == "AssemblyOriginatorKeyFile").Should().Be(1); + mockProj.Properties.First(p => p.Name == "AssemblyOriginatorKeyFile").Value.Should().Be("../keyfile.snk"); + + mockProj.Properties.Count(p => p.Name == "SignAssembly").Should().Be(1); + mockProj.Properties.First(p => p.Name == "SignAssembly").Value.Should().Be("true"); + } + + [Fact] + public void Migrating_delaySign_populates_DelaySign() + { + var mockProj = RunBuildOptionsRuleOnPj(@" + { + ""buildOptions"": { + ""delaySign"": ""true"" + } + }"); + + mockProj.Properties.Count(p => p.Name == "DelaySign").Should().Be(1); + mockProj.Properties.First(p => p.Name == "DelaySign").Value.Should().Be("true"); + + mockProj = RunBuildOptionsRuleOnPj(@" + { + ""buildOptions"": { + ""delaySign"": ""false"" + } + }"); + + mockProj.Properties.Count(p => p.Name == "DelaySign").Should().Be(0); + } + + [Fact] + public void Migrating_publicSign_populates_PublicSign() + { + var mockProj = RunBuildOptionsRuleOnPj(@" + { + ""buildOptions"": { + ""publicSign"": ""true"" + } + }"); + + mockProj.Properties.Count(p => p.Name == "PublicSign").Should().Be(1); + mockProj.Properties.First(p => p.Name == "PublicSign").Value.Should().Be("true"); + + mockProj = RunBuildOptionsRuleOnPj(@" + { + ""buildOptions"": { + ""publicSign"": ""false"" + } + }"); + + mockProj.Properties.Count(p => p.Name == "PublicSign").Should().Be(0); + } + + [Fact] + public void Migrating_debugType_populates_DebugType() + { + var mockProj = RunBuildOptionsRuleOnPj(@" + { + ""buildOptions"": { + ""debugType"": ""full"" + } + }"); + + mockProj.Properties.Count(p => p.Name == "DebugType").Should().Be(1); + mockProj.Properties.First(p => p.Name == "DebugType").Value.Should().Be("full"); + + mockProj = RunBuildOptionsRuleOnPj(@" + { + ""buildOptions"": { + ""debugType"": ""foo"" + } + }"); + + mockProj.Properties.Count(p => p.Name == "DebugType").Should().Be(1); + mockProj.Properties.First(p => p.Name == "DebugType").Value.Should().Be("foo"); + } + + [Fact] + public void Migrating_outputName_populates_AssemblyName() + { + var mockProj = RunBuildOptionsRuleOnPj(@" + { + ""buildOptions"": { + ""outputName"": ""ARandomName"" + } + }"); + + mockProj.Properties.Count(p => p.Name == "AssemblyName").Should().Be(1); + mockProj.Properties.First(p => p.Name == "AssemblyName").Value.Should().Be("ARandomName"); + } + + [Fact] + public void Migrating_xmlDoc_populates_GenerateDocumentationFile() + { + var mockProj = RunBuildOptionsRuleOnPj(@" + { + ""buildOptions"": { + ""xmlDoc"": ""true"" + } + }"); + + mockProj.Properties.Count(p => p.Name == "GenerateDocumentationFile").Should().Be(1); + mockProj.Properties.First(p => p.Name == "GenerateDocumentationFile").Value.Should().Be("true"); + } + + [Theory] + [InlineData("compile", "Compile")] + [InlineData("embed", "EmbeddedResource")] + [InlineData("copyToOutput", "Content")] + private void Migrating_compile_include_exclude_Populates_compile_item( + string group, + string itemName) + { + var testDirectory = Temp.CreateDirectory().Path; + + Directory.CreateDirectory(Path.Combine(testDirectory, "root")); + Directory.CreateDirectory(Path.Combine(testDirectory, "src")); + File.WriteAllText(Path.Combine(testDirectory, "root", "file1.txt"), "content"); + File.WriteAllText(Path.Combine(testDirectory, "root", "file2.txt"), "content"); + File.WriteAllText(Path.Combine(testDirectory, "root", "file3.txt"), "content"); + File.WriteAllText(Path.Combine(testDirectory, "src", "file1.cs"), "content"); + File.WriteAllText(Path.Combine(testDirectory, "src", "file2.cs"), "content"); + File.WriteAllText(Path.Combine(testDirectory, "src", "file3.cs"), "content"); + File.WriteAllText(Path.Combine(testDirectory, "rootfile.cs"), "content"); + + var pj = @" + { + ""buildOptions"": { + """": { + ""include"": [""root"", ""src"", ""rootfile.cs""], + ""exclude"": [""src"", ""rootfile.cs""], + ""includeFiles"": [""src/file1.cs"", ""src/file2.cs""], + ""excludeFiles"": [""src/file2.cs""] + } + } + }".Replace("", group); + + var mockProj = RunBuildOptionsRuleOnPj(pj, + testDirectory: testDirectory); + + mockProj.Items.Count(i => i.ItemType.Equals(itemName, StringComparison.Ordinal)).Should().Be(2); + + var defaultIncludePatterns = group == "compile" ? ProjectFilesCollection.DefaultCompileBuiltInPatterns + : group == "embed" ? ProjectFilesCollection.DefaultResourcesBuiltInPatterns + : Enumerable.Empty(); + + var defaultExcludePatterns = group == "copyToOutput" ? ProjectFilesCollection.DefaultPublishExcludePatterns + : ProjectFilesCollection.DefaultBuiltInExcludePatterns; + + foreach (var item in mockProj.Items.Where(i => i.ItemType.Equals(itemName, StringComparison.Ordinal))) + { + if (item.Include.Contains(@"src\file1.cs")) + { + item.Include.Should().Be(@"src\file1.cs;src\file2.cs"); + item.Exclude.Should().Be(@"src\file2.cs"); + } + else + { + if (defaultIncludePatterns.Any()) + { + item.Include.Should() + .Be(@"root\**\*;src\**\*;rootfile.cs;" + string.Join(";", defaultIncludePatterns).Replace("/", "\\")); + } + else + { + item.Include.Should() + .Be(@"root\**\*;src\**\*;rootfile.cs"); + } + + if (defaultExcludePatterns.Any()) + { + item.Exclude.Should() + .Be(@"src\**\*;rootfile.cs;" + string.Join(";", defaultExcludePatterns).Replace("/", "\\") + + @";src\file2.cs"); + } + else + { + item.Exclude.Should() + .Be(@"src\**\*;rootfile.cs;src\file2.cs"); + } + } + } + } + + private ProjectRootElement RunBuildOptionsRuleOnPj(string s, string testDirectory = null) + { + testDirectory = testDirectory ?? Temp.CreateDirectory().Path; + return TemporaryProjectFileRuleRunner.RunRules(new IMigrationRule[] + { + new MigrateBuildOptionsRule() + }, s, testDirectory); + } + } +} diff --git a/test/Microsoft.DotNet.ProjectJsonMigration.Tests/Rules/GivenThatIWantToMigrateConfigurations.cs b/test/Microsoft.DotNet.ProjectJsonMigration.Tests/Rules/GivenThatIWantToMigrateConfigurations.cs new file mode 100644 index 000000000..bdf0a54d3 --- /dev/null +++ b/test/Microsoft.DotNet.ProjectJsonMigration.Tests/Rules/GivenThatIWantToMigrateConfigurations.cs @@ -0,0 +1,162 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Microsoft.Build.Construction; +using Microsoft.DotNet.Migration.Tests; +using Microsoft.DotNet.ProjectJsonMigration; +using Microsoft.DotNet.ProjectModel; +using Microsoft.DotNet.Tools.Test.Utilities; +using NuGet.Frameworks; +using Xunit; +using FluentAssertions; +using Microsoft.DotNet.ProjectJsonMigration.Tests; + +namespace Microsoft.DotNet.ProjectJsonMigrationMigration.Tests +{ + public class GivenThatIWantToMigrateConfigurations : TestBase + { + [Fact] + public void Configuration_buildOptions_produce_expected_properties_in_a_group_with_a_condition() + { + var mockProj = RunConfigurationsRuleOnPj(@" + { + ""configurations"": { + ""testconfig"": { + ""buildOptions"": { + ""emitEntryPoint"": ""true"", + ""debugType"": ""full"" + } + } + } + }"); + + mockProj.Properties.Count( + prop => prop.Name == "OutputType" || prop.Name == "TargetExt" || prop.Name == "DebugType").Should().Be(3); + + mockProj.Properties.First(p => p.Name == "OutputType") + .Parent.Condition.Should() + .Contain("'$(Configuration)' == 'testconfig'"); + } + + [Fact] + public void Configuration_buildOptions_properties_are_not_written_when_they_overlap_with_buildOptions() + { + var mockProj = RunConfigurationsAndBuildOptionsRuleOnPj(@" + { + ""buildOptions"": { + ""emitEntryPoint"": ""true"", + ""debugType"": ""full"" + }, + ""configurations"": { + ""testconfig"": { + ""buildOptions"": { + ""emitEntryPoint"": ""true"", + ""debugType"": ""full"" + } + } + } + }"); + + mockProj.Properties.Count(property => + property.Name == "OutputType" || property.Name == "TargetExt" || property.Name == "DebugType") + .Should().Be(3); + + foreach (var property in mockProj.Properties.Where(property => + property.Name == "OutputType" || property.Name == "TargetExt" || property.Name == "DebugType")) + { + property.Parent.Condition.Should().Be(string.Empty); + } + + } + + [Fact] + public void Configuration_buildOptions_includes_are_not_written_when_they_overlap_with_buildOptions() + { + var mockProj = RunConfigurationsAndBuildOptionsRuleOnPj(@" + { + ""buildOptions"": { + ""copyToOutput"": { + ""include"": [""src""], + ""exclude"": [""src"", ""rootfile.cs""], + ""includeFiles"": [""src/file1.cs"", ""src/file2.cs""], + ""excludeFiles"": [""src/file2.cs""] + } + }, + ""configurations"": { + ""testconfig"": { + ""buildOptions"": { + ""copyToOutput"": { + ""include"": [""root"", ""src"", ""rootfile.cs""], + ""exclude"": [""src"", ""rootfile.cs""], + ""includeFiles"": [""src/file1.cs"", ""src/file2.cs""], + ""excludeFiles"": [""src/file2.cs""] + } + } + } + } + }"); + + mockProj.Items.Count(item => item.ItemType == "Content").Should().Be(3); + + mockProj.Items.Where(item => item.ItemType == "Content") + .Count(item => !string.IsNullOrEmpty(item.Parent.Condition)) + .Should() + .Be(1); + + var configContent = mockProj.Items + .Where(item => item.ItemType == "Content").First(item => !string.IsNullOrEmpty(item.Parent.Condition)); + + // 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"); + } + + [Fact] + public void Configuration_buildOptions_includes_which_have_different_excludes_than_buildOptions_throws() + { + Action action = () => RunConfigurationsRuleOnPj(@" + { + ""buildOptions"": { + ""copyToOutput"": { + ""include"": [""src""], + ""exclude"": [""rootfile.cs""], + ""includeFiles"": [""src/file1.cs"", ""src/file2.cs""], + ""excludeFiles"": [""src/file3.cs""] + } + }, + ""configurations"": { + ""testconfig"": { + ""buildOptions"": { + ""copyToOutput"": { + ""include"": [""root"", ""src"", ""rootfile.cs""], + ""exclude"": [""src"", ""rootfile.cs""], + ""includeFiles"": [""src/file1.cs"", ""src/file2.cs""], + ""excludeFiles"": [""src/file2.cs""] + } + } + } + } + }"); + + action.ShouldThrow() + .WithMessage( + "Unable to migrate projects with excluded files in configurations."); + } + private ProjectRootElement RunConfigurationsRuleOnPj(string s, string testDirectory = null) + { + testDirectory = testDirectory ?? Temp.CreateDirectory().Path; + return TemporaryProjectFileRuleRunner.RunRules(new[] {new MigrateConfigurationsRule()}, s, testDirectory); + } + + private ProjectRootElement RunConfigurationsAndBuildOptionsRuleOnPj(string s, string testDirectory = null) + { + testDirectory = testDirectory ?? Temp.CreateDirectory().Path; + return TemporaryProjectFileRuleRunner.RunRules(new IMigrationRule[] + { + new MigrateBuildOptionsRule(), + new MigrateConfigurationsRule() + }, s, testDirectory); + } + } +} diff --git a/test/Microsoft.DotNet.ProjectJsonMigration.Tests/Rules/GivenThatIWantToMigrateProjectDependencies.cs b/test/Microsoft.DotNet.ProjectJsonMigration.Tests/Rules/GivenThatIWantToMigrateProjectDependencies.cs new file mode 100644 index 000000000..532a934f9 --- /dev/null +++ b/test/Microsoft.DotNet.ProjectJsonMigration.Tests/Rules/GivenThatIWantToMigrateProjectDependencies.cs @@ -0,0 +1,85 @@ +using Microsoft.Build.Construction; +using Microsoft.DotNet.ProjectJsonMigration; +using Microsoft.DotNet.ProjectModel; +using Microsoft.DotNet.Tools.Test.Utilities; +using NuGet.Frameworks; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Threading.Tasks; +using Xunit; +using FluentAssertions; + +namespace Microsoft.DotNet.Migration.Tests +{ + public class GivenThatIWantToMigrateProjectDependencies : TestBase + { + // Workaround For P2P dependencies + // ISSUE: https://github.com/dotnet/sdk/issues/73 + [Fact] + public void If_a_project_dependency_is_present_DesignTimeAutoUnify_and_AutoUnify_are_present() + { + var solutionDirectory = + TestAssetsManager.CreateTestInstance("TestAppWithLibrary", callingMethod: "p").WithLockFiles().Path; + + var appDirectory = Path.Combine(solutionDirectory, "TestApp"); + var libDirectory = Path.Combine(solutionDirectory, "TestLibrary"); + + var projectContext = ProjectContext.Create(appDirectory, FrameworkConstants.CommonFrameworks.NetCoreApp10); + var mockProj = ProjectRootElement.Create(); + var testSettings = new MigrationSettings(appDirectory, appDirectory, "1.0.0", mockProj); + var testInputs = new MigrationRuleInputs(new[] {projectContext}, mockProj, mockProj.AddItemGroup(), + mockProj.AddPropertyGroup()); + new MigrateProjectDependenciesRule().Apply(testSettings, testInputs); + + var autoUnify = mockProj.Properties.Where(p => p.Name == "AutoUnify"); + autoUnify.Count().Should().Be(1); + autoUnify.First().Value.Should().Be("true"); + + var designTimeAutoUnify = mockProj.Properties.Where(p => p.Name == "DesignTimeAutoUnify"); + designTimeAutoUnify.Count().Should().Be(1); + designTimeAutoUnify.First().Value.Should().Be("true"); + } + + [Fact] + public void Project_dependencies_are_migrated_to_ProjectReference() + { + var solutionDirectory = + TestAssetsManager.CreateTestInstance("TestAppWithLibrary", callingMethod: "p").WithLockFiles().Path; + + var appDirectory = Path.Combine(solutionDirectory, "TestApp"); + + var projectContext = ProjectContext.Create(appDirectory, FrameworkConstants.CommonFrameworks.NetCoreApp10); + var mockProj = ProjectRootElement.Create(); + var testSettings = new MigrationSettings(appDirectory, appDirectory, "1.0.0", mockProj); + var testInputs = new MigrationRuleInputs(new[] {projectContext}, mockProj, mockProj.AddItemGroup(), + mockProj.AddPropertyGroup()); + new MigrateProjectDependenciesRule().Apply(testSettings, testInputs); + + var projectReferences = mockProj.Items.Where(item => item.ItemType.Equals("ProjectReference", StringComparison.Ordinal)); + projectReferences.Count().Should().Be(1); + + projectReferences.First().Include.Should().Be("../TestLibrary/TestLibrary.csproj"); + } + + [Fact] + public void It_throws_when_project_dependency_is_unresolved() + { + // No Lock file => unresolved + var solutionDirectory = + TestAssetsManager.CreateTestInstance("TestAppWithLibrary").Path; + + var appDirectory = Path.Combine(solutionDirectory, "TestApp"); + + var projectContext = ProjectContext.Create(appDirectory, FrameworkConstants.CommonFrameworks.NetCoreApp10); + var mockProj = ProjectRootElement.Create(); + var testSettings = new MigrationSettings(appDirectory, appDirectory, "1.0.0", mockProj); + var testInputs = new MigrationRuleInputs(new[] {projectContext}, mockProj, mockProj.AddItemGroup(), mockProj.AddPropertyGroup()); + + Action action = () => new MigrateProjectDependenciesRule().Apply(testSettings, testInputs); + action.ShouldThrow() + .WithMessage("Cannot migrate unresolved project dependency, please ensure restore has been run."); + } + } +} diff --git a/test/Microsoft.DotNet.ProjectJsonMigration.Tests/Rules/GivenThatIWantToMigratePublishOptions.cs b/test/Microsoft.DotNet.ProjectJsonMigration.Tests/Rules/GivenThatIWantToMigratePublishOptions.cs new file mode 100644 index 000000000..d9fe2544b --- /dev/null +++ b/test/Microsoft.DotNet.ProjectJsonMigration.Tests/Rules/GivenThatIWantToMigratePublishOptions.cs @@ -0,0 +1,149 @@ +using System; +using System.IO; +using System.Linq; +using Microsoft.Build.Construction; +using Microsoft.DotNet.Migration.Tests; +using Microsoft.DotNet.ProjectModel; +using Microsoft.DotNet.ProjectModel.Files; +using Microsoft.DotNet.TestFramework; +using Microsoft.DotNet.Tools.Test.Utilities; +using NuGet.Frameworks; +using Xunit; +using FluentAssertions; + +namespace Microsoft.DotNet.ProjectJsonMigration.Tests +{ + public class GivenThatIWantToMigratePublishOptions : TestBase + { + [Fact] + private void Migrating_publishOptions_include_exclude_populates_Content_item() + { + var testDirectory = Temp.CreateDirectory().Path; + WriteFilesInProjectDirectory(testDirectory); + + var mockProj = RunPublishOptionsRuleOnPj(@" + { + ""publishOptions"": { + ""include"": [""root"", ""src"", ""rootfile.cs""], + ""exclude"": [""src"", ""rootfile.cs""], + ""includeFiles"": [""src/file1.cs"", ""src/file2.cs""], + ""excludeFiles"": [""src/file2.cs""] + } + }", + testDirectory: testDirectory); + + mockProj.Items.Count(i => i.ItemType.Equals("Content", StringComparison.Ordinal)).Should().Be(2); + + // From ProjectReader #L725 (Both are empty) + var defaultIncludePatterns = Enumerable.Empty(); + var defaultExcludePatterns = ProjectFilesCollection.DefaultPublishExcludePatterns; + + foreach (var item in mockProj.Items.Where(i => i.ItemType.Equals("Content", StringComparison.Ordinal))) + { + if (item.Include.Contains(@"src\file1.cs")) + { + item.Include.Should().Be(@"src\file1.cs;src\file2.cs"); + item.Exclude.Should().Be(@"src\file2.cs"); + } + else + { + item.Include.Should() + .Be(@"root\**\*;src\**\*;rootfile.cs"); + + item.Exclude.Should() + .Be(@"src\**\*;rootfile.cs;src\file2.cs"); + } + } + } + + [Fact] + private void Migrating_publishOptions_and_buildOptions_CopyToOutput_merges_Content_items() + { + var testDirectory = Temp.CreateDirectory().Path; + WriteFilesInProjectDirectory(testDirectory); + + var mockProj = RunPublishAndBuildOptionsRuleOnPj(@" + { + ""buildOptions"": { + ""copyToOutput"": { + ""include"": [""src"", ""rootfile.cs""], + ""exclude"": [""src"", ""rootfile.cs""], + ""includeFiles"": [""src/file1.cs"", ""src/file2.cs""], + ""excludeFiles"": [""src/file2.cs""] + } + }, + ""publishOptions"": { + ""include"": [""root"", ""src"", ""rootfile.cs""], + ""exclude"": [""src"", ""rootfile.cs""], + ""includeFiles"": [""src/file1.cs"", ""src/file2.cs""], + ""excludeFiles"": [""src/file3.cs""] + } + }", + testDirectory: testDirectory); + + Console.WriteLine(string.Join(";", mockProj.Items.Select(i => " ;; " + i.ItemType))); + Console.WriteLine(string.Join(";", mockProj.Items.Select(i => " ;; " + i.Include))); + Console.WriteLine(string.Join(";", mockProj.Items.Select(i => " ;; " + i.Exclude))); + + mockProj.Items.Count(i => i.ItemType.Equals("Content", StringComparison.Ordinal)).Should().Be(3); + + // From ProjectReader #L725 (Both are empty) + var defaultIncludePatterns = Enumerable.Empty(); + var defaultExcludePatterns = ProjectFilesCollection.DefaultPublishExcludePatterns; + + foreach (var item in mockProj.Items.Where(i => i.ItemType.Equals("Content", StringComparison.Ordinal))) + { + if (item.Include.Contains(@"root\**\*")) + { + item.Include.Should().Be(@"root\**\*"); + item.Exclude.Should().Be(@"src\**\*;rootfile.cs;src\file3.cs"); + } + else if (item.Include.Contains(@"src\file1.cs")) + { + item.Include.Should().Be(@"src\file1.cs;src\file2.cs"); + item.Exclude.Should().Be(@"src\file2.cs;src\file3.cs"); + } + else + { + item.Include.Should() + .Be(@"src\**\*;rootfile.cs"); + + item.Exclude.Should() + .Be(@"src\**\*;rootfile.cs;src\file2.cs;src\file3.cs"); + } + } + } + + private void WriteFilesInProjectDirectory(string testDirectory) + { + Directory.CreateDirectory(Path.Combine(testDirectory, "root")); + Directory.CreateDirectory(Path.Combine(testDirectory, "src")); + File.WriteAllText(Path.Combine(testDirectory, "root", "file1.txt"), "content"); + File.WriteAllText(Path.Combine(testDirectory, "root", "file2.txt"), "content"); + File.WriteAllText(Path.Combine(testDirectory, "root", "file3.txt"), "content"); + File.WriteAllText(Path.Combine(testDirectory, "src", "file1.cs"), "content"); + File.WriteAllText(Path.Combine(testDirectory, "src", "file2.cs"), "content"); + File.WriteAllText(Path.Combine(testDirectory, "src", "file3.cs"), "content"); + File.WriteAllText(Path.Combine(testDirectory, "rootfile.cs"), "content"); + } + + private ProjectRootElement RunPublishOptionsRuleOnPj(string s, string testDirectory = null) + { + testDirectory = testDirectory ?? Temp.CreateDirectory().Path; + return TemporaryProjectFileRuleRunner.RunRules(new IMigrationRule[] + { + new MigratePublishOptionsRule() + }, s, testDirectory); + } + + private ProjectRootElement RunPublishAndBuildOptionsRuleOnPj(string s, string testDirectory = null) + { + testDirectory = testDirectory ?? Temp.CreateDirectory().Path; + return TemporaryProjectFileRuleRunner.RunRules(new IMigrationRule[] + { + new MigrateBuildOptionsRule(), + new MigratePublishOptionsRule() + }, s, testDirectory); + } + } +} \ No newline at end of file diff --git a/test/Microsoft.DotNet.ProjectJsonMigration.Tests/Rules/GivenThatIWantToMigrateRuntimeOptions.cs b/test/Microsoft.DotNet.ProjectJsonMigration.Tests/Rules/GivenThatIWantToMigrateRuntimeOptions.cs new file mode 100644 index 000000000..8089fef95 --- /dev/null +++ b/test/Microsoft.DotNet.ProjectJsonMigration.Tests/Rules/GivenThatIWantToMigrateRuntimeOptions.cs @@ -0,0 +1,66 @@ +// Copyright (c) .NET Foundation and contributors. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System.Linq; +using FluentAssertions; +using Microsoft.DotNet.Tools.Test.Utilities; +using Xunit; +using Microsoft.DotNet.ProjectJsonMigration; +using System; +using System.IO; +using Microsoft.Build.Construction; +using Microsoft.DotNet.ProjectModel; +using NuGet.Frameworks; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; +using System.Collections.Generic; + +namespace Microsoft.DotNet.ProjectJsonMigration.Tests +{ + public class GivenThatIWantToMigrateRuntimeOptions : TestBase + { + private static readonly string s_runtimeConfigFileName = "runtimeconfig.template.json"; + + [Fact] + public void RuntimeOptions_are_copied_from_projectJson_to_runtimeconfig_template_json_file() + { + var testInstance = TestAssetsManager.CreateTestInstance("TestAppWithRuntimeOptions").WithLockFiles(); + var projectDir = testInstance.Path; + var projectPath = Path.Combine(testInstance.Path, "project.json"); + + var project = JObject.Parse(File.ReadAllText(projectPath)); + var rawRuntimeOptions = (JObject)project.GetValue("runtimeOptions"); + + var projectContext = ProjectContext.Create(projectDir, FrameworkConstants.CommonFrameworks.NetCoreApp10); + + var testSettings = new MigrationSettings(projectDir, projectDir, "1.0.0", default(ProjectRootElement)); + var testInputs = new MigrationRuleInputs(new[] { projectContext }, null, null, null); + new MigrateRuntimeOptionsRule().Apply(testSettings, testInputs); + + var migratedRuntimeOptionsPath = Path.Combine(projectDir, s_runtimeConfigFileName); + + File.Exists(migratedRuntimeOptionsPath).Should().BeTrue(); + Console.WriteLine(migratedRuntimeOptionsPath); + + var migratedRuntimeOptionsContent = JObject.Parse(File.ReadAllText(migratedRuntimeOptionsPath)); + JToken.DeepEquals(rawRuntimeOptions, migratedRuntimeOptionsContent).Should().BeTrue(); + } + + [Fact] + public void Migrating_ProjectJson_with_no_RuntimeOptions_produces_no_runtimeconfig_template_json_file() + { + var testInstance = TestAssetsManager.CreateTestInstance("TestAppSimple").WithLockFiles(); + var projectDir = testInstance.Path; + + var projectContext = ProjectContext.Create(projectDir, FrameworkConstants.CommonFrameworks.NetCoreApp10); + + var testSettings = new MigrationSettings(projectDir, projectDir, "1.0.0", default(ProjectRootElement)); + var testInputs = new MigrationRuleInputs(new[] { projectContext }, null, null, null); + new MigrateRuntimeOptionsRule().Apply(testSettings, testInputs); + + var migratedRuntimeOptionsPath = Path.Combine(projectDir, s_runtimeConfigFileName); + + File.Exists(migratedRuntimeOptionsPath).Should().BeFalse(); + } + } +} diff --git a/test/Microsoft.DotNet.ProjectJsonMigration.Tests/Rules/GivenThatIWantToMigrateScripts.cs b/test/Microsoft.DotNet.ProjectJsonMigration.Tests/Rules/GivenThatIWantToMigrateScripts.cs new file mode 100644 index 000000000..ceea3d2df --- /dev/null +++ b/test/Microsoft.DotNet.ProjectJsonMigration.Tests/Rules/GivenThatIWantToMigrateScripts.cs @@ -0,0 +1,205 @@ +// Copyright (c) .NET Foundation and contributors. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System.Linq; +using FluentAssertions; +using Microsoft.DotNet.Tools.Test.Utilities; +using Xunit; +using Microsoft.DotNet.ProjectJsonMigration; +using System; +using System.IO; +using Microsoft.Build.Construction; + +namespace Microsoft.DotNet.ProjectJsonMigration.Tests +{ + public class GivenThatIWantToMigrateScripts : TestBase + { + [Theory] + [InlineData("compile:FullTargetFramework", "$(TargetFrameworkIdentifier)=$(TargetFrameworkVersion)")] + [InlineData("compile:Configuration", "$(Configuration)")] + [InlineData("compile:OutputFile", "$(TargetPath)")] + [InlineData("compile:OutputDir", "$(TargetDir)")] + [InlineData("publish:ProjectPath", "$(MSBuildThisFileDirectory)")] + [InlineData("publish:Configuration", "$(Configuration)")] + [InlineData("publish:OutputPath", "$(TargetDir)")] + [InlineData("publish:FullTargetFramework", "$(TargetFrameworkIdentifier)=$(TargetFrameworkVersion)")] + [InlineData("project:Version", "$(Version)")] + [InlineData("project:Name", "$(MSBuildThisFileName)")] + [InlineData("project:Directory", "$(MSBuildProjectDirectory)")] + public void Formatting_script_commands_replaces_variables_with_the_right_msbuild_properties( + string variable, + string msbuildReplacement) + { + var scriptMigrationRule = new MigrateScriptsRule(); + scriptMigrationRule.ReplaceScriptVariables($"%{variable}%").Should().Be(msbuildReplacement); + } + + [Theory] + [InlineData("compile:TargetFramework")] + [InlineData("compile:ResponseFile")] + [InlineData("compile:CompilerExitCode")] + [InlineData("compile:RuntimeOutputDir")] + [InlineData("compile:RuntimeIdentifier")] + [InlineData("publish:TargetFramework")] + [InlineData("publish:Runtime")] + public void Formatting_script_commands_throws_when_variable_is_unsupported(string unsupportedVariable) + { + var scriptMigrationRule = new MigrateScriptsRule(); + + Action formatScriptAction = () => scriptMigrationRule.ReplaceScriptVariables($"%{unsupportedVariable}%"); + formatScriptAction.ShouldThrow() + .Where(exc => exc.Message.Contains("is currently an unsupported script variable for project migration")); + } + + [Theory] + [InlineData("precompile", "Build")] + [InlineData("prepublish", "Publish")] + public void Migrating_pre_scripts_populates_BeforeTargets_with_appropriate_target(string scriptName, string targetName) + { + var scriptMigrationRule = new MigrateScriptsRule(); + ProjectRootElement mockProj = ProjectRootElement.Create(); + var commands = new string[] { "fakecommand" }; + + var target = scriptMigrationRule.MigrateScriptSet(mockProj, mockProj.AddPropertyGroup(), commands, scriptName); + + target.BeforeTargets.Should().Be(targetName); + } + + [Theory] + [InlineData("postcompile", "Build")] + [InlineData("postpublish", "Publish")] + public void Migrating_post_scripts_populates_AfterTargets_with_appropriate_target(string scriptName, string targetName) + { + var scriptMigrationRule = new MigrateScriptsRule(); + ProjectRootElement mockProj = ProjectRootElement.Create(); + var commands = new string[] { "fakecommand" }; + + var target = scriptMigrationRule.MigrateScriptSet(mockProj, mockProj.AddPropertyGroup(), commands, scriptName); + + target.AfterTargets.Should().Be(targetName); + } + + [Theory] + [InlineData("precompile")] + [InlineData("postcompile")] + [InlineData("prepublish")] + [InlineData("postpublish")] + public void Migrating_scripts_with_multiple_commands_creates_Exec_task_for_each(string scriptName) + { + var scriptMigrationRule = new MigrateScriptsRule(); + ProjectRootElement mockProj = ProjectRootElement.Create(); + + var commands = new string[] { "fakecommand1", "fakecommand2", "mockcommand3" }; + var commandsInTask = commands.ToDictionary(c => c, c => false); + + var target = scriptMigrationRule.MigrateScriptSet(mockProj, mockProj.AddPropertyGroup(), commands, scriptName); + + foreach (var task in target.Tasks) + { + var taskCommand = task.GetParameter("Command"); + var originalCommandCandidates = commands.Where(c => taskCommand.Contains(c)); + originalCommandCandidates.Count().Should().Be(1); + + var command = originalCommandCandidates.First(); + commandsInTask[command].Should().Be(false, "Expected to find each element from commands Array once"); + + commandsInTask[command] = true; + } + + commandsInTask.All(commandInTask => commandInTask.Value) + .Should() + .BeTrue("Expected each element from commands array to be found in a task"); + } + + [Theory] + [InlineData("precompile")] + [InlineData("postcompile")] + [InlineData("prepublish")] + [InlineData("postpublish")] + public void Migrated_ScriptSet_has_Exec_and_replaces_variables(string scriptName) + { + var scriptMigrationRule = new MigrateScriptsRule(); + ProjectRootElement mockProj = ProjectRootElement.Create(); + + var commands = new string[] { "compile:FullTargetFramework", "compile:Configuration"}; + + var target = scriptMigrationRule.MigrateScriptSet(mockProj, mockProj.AddPropertyGroup(), commands, scriptName); + target.Tasks.Count().Should().Be(commands.Length); + + foreach (var task in target.Tasks) + { + var taskCommand = task.GetParameter("Command"); + var commandIndex = Array.IndexOf(commands, taskCommand); + + commandIndex.Should().Be(-1, "Expected command array elements to be replaced by appropriate msbuild properties"); + } + } + + [Theory] + [InlineData("precompile")] + [InlineData("postcompile")] + [InlineData("prepublish")] + [InlineData("postpublish")] + public void Migrated_ScriptSet_has_two_MigratedScriptExtensionProperties_for_each_script(string scriptName) + { + var scriptMigrationRule = new MigrateScriptsRule(); + ProjectRootElement mockProj = ProjectRootElement.Create(); + + var commands = new string[] {"compile:FullTargetFramework", "compile:Configuration"}; + var propertyGroup = mockProj.AddPropertyGroup(); + var target = scriptMigrationRule.MigrateScriptSet(mockProj, propertyGroup, commands, + scriptName); + + Console.WriteLine(string.Join(";", propertyGroup.Properties.Select(n => n.Name))); + propertyGroup.Properties.Count().Should().Be(commands.Length * 2); + + var count = 0; + foreach (var command in commands) + { + count += 1; + var scriptExtensionProperties = + propertyGroup.Properties.Where(p => p.Name.Contains($"MigratedScriptExtension_{count}")).ToArray(); + + scriptExtensionProperties.All(p => p.Value == ".sh" || p.Value == ".cmd").Should().BeTrue(); + scriptExtensionProperties.Count().Should().Be(2); + } + } + + [Theory] + [InlineData("echo", ".\\echo$(MigratedScriptExtension_1)")] + [InlineData("echo hello world", ".\\echo$(MigratedScriptExtension_1) hello world")] + [InlineData("\"echo\"", ".\\\"echo$(MigratedScriptExtension_1)\"")] + [InlineData("\"echo space\"", ".\\\"echo space$(MigratedScriptExtension_1)\"")] + [InlineData("\"echo space\" other args", ".\\\"echo space$(MigratedScriptExtension_1)\" other args")] + [InlineData("\"echo space\" \"other space\"", ".\\\"echo space$(MigratedScriptExtension_1)\" \"other space\"")] + public void Migrated_ScriptSet_has_ScriptExtension_added_to_script_command(string scriptCommandline, string expectedOutputCommand) + { + var scriptMigrationRule = new MigrateScriptsRule(); + ProjectRootElement mockProj = ProjectRootElement.Create(); + + var formattedCommand = scriptMigrationRule.AddScriptExtensionPropertyToCommandLine(scriptCommandline, + "MigratedScriptExtension_1"); + + formattedCommand.Should().Be(expectedOutputCommand); + } + + [Theory] + [InlineData("echo", @".\echo")] + [InlineData("/usr/echo", "/usr/echo")] + [InlineData(@"C:\usr\echo", @"C:\usr\echo")] + [InlineData("\"echo\"", @".\""echo")] + [InlineData("\"/usr/echo\"", @"""/usr/echo")] + [InlineData(@"""C:\usr\echo", @"""C:\usr\echo")] + public void Migrated_ScriptSet_has_dotSlash_prepended_when_command_is_not_rooted(string scriptCommandline, + string expectedOutputCommandPrefix) + { + var scriptMigrationRule = new MigrateScriptsRule(); + ProjectRootElement mockProj = ProjectRootElement.Create(); + + var formattedCommand = scriptMigrationRule.FormatScriptCommand(scriptCommandline, + "MigratedScriptExtension_1"); + + formattedCommand.Should().StartWith(expectedOutputCommandPrefix); + } + } +} diff --git a/test/Microsoft.DotNet.ProjectJsonMigration.Tests/Rules/GivenThatIWantToMigrateTFMs.cs b/test/Microsoft.DotNet.ProjectJsonMigration.Tests/Rules/GivenThatIWantToMigrateTFMs.cs new file mode 100644 index 000000000..2018aec96 --- /dev/null +++ b/test/Microsoft.DotNet.ProjectJsonMigration.Tests/Rules/GivenThatIWantToMigrateTFMs.cs @@ -0,0 +1,43 @@ +using Microsoft.Build.Construction; +using Microsoft.DotNet.ProjectJsonMigration; +using Microsoft.DotNet.ProjectModel; +using Microsoft.DotNet.Tools.Test.Utilities; +using NuGet.Frameworks; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Xunit; +using FluentAssertions; + +namespace Microsoft.DotNet.ProjectJsonMigration.Tests +{ + public class GivenThatIWantToMigrateProjectFramework : TestBase + { + [Fact] + public void Migrating_netcoreapp_project_Populates_TargetFrameworkIdentifier_and_TargetFrameworkVersion() + { + var testDirectory = Temp.CreateDirectory().Path; + var testPJ = new ProjectJsonBuilder(TestAssetsManager) + .FromDefaultBase() + .WithCustomProperty("buildOptions", new Dictionary + { + { "emitEntryPoint", "false" } + }) + .SaveToDisk(testDirectory); + + var projectContext = ProjectContext.Create(testDirectory, FrameworkConstants.CommonFrameworks.NetCoreApp10); + var mockProj = ProjectRootElement.Create(); + + // Run BuildOptionsRule + var testSettings = new MigrationSettings(testDirectory, testDirectory, "1.0.0", mockProj); + var testInputs = new MigrationRuleInputs(new[] { projectContext }, mockProj, mockProj.AddItemGroup(), mockProj.AddPropertyGroup()); + new MigrateTFMRule().Apply(testSettings, testInputs); + + mockProj.Properties.Count(p => p.Name == "TargetFrameworkIdentifier").Should().Be(1); + mockProj.Properties.Count(p => p.Name == "TargetFrameworkVersion").Should().Be(1); + mockProj.Properties.First(p => p.Name == "TargetFrameworkIdentifier").Value.Should().Be(".NETCoreApp"); + mockProj.Properties.First(p => p.Name == "TargetFrameworkVersion").Value.Should().Be("v1.0"); + } + } +} diff --git a/test/Microsoft.DotNet.ProjectJsonMigration.Tests/Scenarios/GivenThatIWantToMigrateTestApps.cs b/test/Microsoft.DotNet.ProjectJsonMigration.Tests/Scenarios/GivenThatIWantToMigrateTestApps.cs new file mode 100644 index 000000000..dbb2b7017 --- /dev/null +++ b/test/Microsoft.DotNet.ProjectJsonMigration.Tests/Scenarios/GivenThatIWantToMigrateTestApps.cs @@ -0,0 +1,212 @@ +using Microsoft.Build.Construction; +using Microsoft.DotNet.ProjectJsonMigration; +using Microsoft.DotNet.ProjectModel; +using Microsoft.DotNet.Tools.Test.Utilities; +using NuGet.Frameworks; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Xunit; +using FluentAssertions; +using System.IO; +using Microsoft.DotNet.Tools.Common; +using Microsoft.DotNet.Cli; +using Microsoft.DotNet.Cli.Utils; +using Build3Command = Microsoft.DotNet.Tools.Test.Utilities.Build3Command; +using TemporaryDotnetNewTemplateProject = Microsoft.DotNet.Cli.TemporaryDotnetNewTemplateProject; + +namespace Microsoft.DotNet.Migration.Tests +{ + public class GivenThatIWantToMigrateTestApps : TestBase + { + private class Failure + { + public string Phase {get; set;} + public string Message {get; set;} + public string ProjectJson {get; set;} + } + + [Theory] + // TODO: Standalone apps [InlineData("TestAppSimple", false)] + // https://github.com/dotnet/sdk/issues/73 [InlineData("TestAppWithLibrary/TestApp", false)] + [InlineData("TestAppWithRuntimeOptions", false)] + public void It_migrates_a_project(string projectName, bool isLibrary) + { + var projectDirectory = TestAssetsManager.CreateTestInstance(projectName, callingMethod: "i").WithLockFiles().Path; + + BuildProjectJson(projectDirectory); + var projectJsonBuildOutputs = new HashSet(CollectBuildOutputs(projectDirectory)); + CleanBinObj(projectDirectory); + + MigrateProject(projectDirectory); + Restore(projectDirectory); + BuildMSBuild(projectDirectory); + + var msbuildBuildOutputs = new HashSet(CollectBuildOutputs(projectDirectory)); + + var outputsIdentical = projectJsonBuildOutputs.SetEquals(msbuildBuildOutputs); + + // diagnostics + if (!outputsIdentical) + { + Console.WriteLine("Project.json Outputs:"); + Console.WriteLine(string.Join("\n", projectJsonBuildOutputs)); + + Console.WriteLine(""); + + Console.WriteLine("MSBuild Outputs:"); + Console.WriteLine(string.Join("\n", msbuildBuildOutputs)); + } + + outputsIdentical.Should().BeTrue(); + + if (!isLibrary) + { + VerifyMSBuildOutputRunnable(projectDirectory); + } + } + + [Theory] + [InlineData("TestAppWithLibrary/TestLibrary")] + [InlineData("TestLibraryWithAnalyzer")] + [InlineData("TestLibraryWithConfiguration")] + public void It_migrates_a_library(string projectName) + { + var projectDirectory = + TestAssetsManager.CreateTestInstance(projectName, callingMethod: "i").WithLockFiles().Path; + + BuildProjectJson(projectDirectory); + var projectJsonBuildOutputs = new HashSet(CollectBuildOutputs(projectDirectory)); + CleanBinObj(projectDirectory); + + MigrateProject(projectDirectory); + Restore(projectDirectory); + BuildMSBuild(projectDirectory); + + var msbuildBuildOutputs = new HashSet(CollectBuildOutputs(projectDirectory)); + + var msBuildHasAdditionalOutputsButIncludesProjectJsonOutputs = projectJsonBuildOutputs.IsProperSubsetOf(msbuildBuildOutputs); + + // diagnostics + if (!msBuildHasAdditionalOutputsButIncludesProjectJsonOutputs) + { + Console.WriteLine("Project.json Outputs:"); + Console.WriteLine(string.Join("\n", projectJsonBuildOutputs)); + + Console.WriteLine(""); + + Console.WriteLine("MSBuild Outputs:"); + Console.WriteLine(string.Join("\n", msbuildBuildOutputs)); + } + + msBuildHasAdditionalOutputsButIncludesProjectJsonOutputs.Should().BeTrue(); + } + + [Fact] + public void It_migrates_an_app_with_scripts_and_the_scripts_run() + { + var projectDirectory = + TestAssetsManager.CreateTestInstance("TestAppWithMigrateAbleScripts", callingMethod: "i").WithLockFiles().Path; + + BuildProjectJson(projectDirectory); + var projectJsonBuildOutputs = new HashSet(CollectBuildOutputs(projectDirectory)); + CleanBinObj(projectDirectory); + + MigrateProject(projectDirectory); + Restore(projectDirectory); + var msBuildStdOut = BuildMSBuild(projectDirectory); + + var msbuildBuildOutputs = new HashSet(CollectBuildOutputs(projectDirectory)); + + var outputsIdentical = projectJsonBuildOutputs.SetEquals(msbuildBuildOutputs); + outputsIdentical.Should().BeTrue(); + VerifyMSBuildOutputRunnable(projectDirectory); + + var outputDir = + PathUtility.EnsureTrailingSlash(Path.Combine(projectDirectory, "bin", "Debug", "netcoreapp1.0")); + + msBuildStdOut.Should().Contain($"precompile_output ?Debug? ?{outputDir}? ?.NETCoreApp=v1.0?"); + msBuildStdOut.Should().Contain($"postcompile_output ?Debug? ?{outputDir}? ?.NETCoreApp=v1.0?"); + } + + private string RunNetcoreappMSBuildOutput(string projectDirectory) + { + var dllFileName = Path.GetFileName(projectDirectory) + ".dll"; + + var runnableDll = Path.Combine(projectDirectory, "bin","Debug", "netcoreapp1.0", dllFileName); + var result = new TestCommand("dotnet").ExecuteWithCapturedOutput(runnableDll); + result.Should().Pass(); + return result.StdOut; + } + + private void VerifyMSBuildOutputRunnable(string projectDirectory) + { + var dllFileName = Path.GetFileName(projectDirectory) + ".dll"; + + var runnableDlls = Directory.EnumerateFiles(Path.Combine(projectDirectory, "bin"), dllFileName, + SearchOption.AllDirectories); + + foreach (var dll in runnableDlls) + { + new TestCommand("dotnet").ExecuteWithCapturedOutput(dll).Should().Pass(); + } + } + + private IEnumerable CollectBuildOutputs(string projectDirectory) + { + var fullBinPath = Path.GetFullPath(Path.Combine(projectDirectory, "bin")); + + return Directory.EnumerateFiles(fullBinPath, "*", SearchOption.AllDirectories) + .Select(p => Path.GetFullPath(p).Substring(fullBinPath.Length)); + } + + private void CleanBinObj(string projectDirectory) + { + var dirs = new string[] { Path.Combine(projectDirectory, "bin"), Path.Combine(projectDirectory, "obj") }; + + foreach (var dir in dirs) + { + Directory.Delete(dir, true); + } + } + + private void BuildProjectJson(string projectDirectory) + { + var projectFile = Path.Combine(projectDirectory, "project.json"); + var result = new BuildCommand(projectPath: projectFile) + .ExecuteWithCapturedOutput(); + + result.Should().Pass(); + } + + private void MigrateProject(string projectDirectory) + { + var dotnetNew = new TemporaryDotnetNewTemplateProject(); + var sdkVersion = new ProjectJsonParser(dotnetNew.ProjectJson).SdkPackageVersion; + var migrationSettings = new MigrationSettings(projectDirectory, projectDirectory, sdkVersion, dotnetNew.MSBuildProject); + new ProjectMigrator().Migrate(migrationSettings); + } + + private void Restore(string projectDirectory) + { + new TestCommand("dotnet") + .WithWorkingDirectory(projectDirectory) + .Execute("restore") + .Should() + .Pass(); + } + + private string BuildMSBuild(string projectDirectory) + { + var result = new Build3Command() + .WithWorkingDirectory(projectDirectory) + .ExecuteWithCapturedOutput(); + result + .Should() + .Pass(); + + return result.StdOut; + } + } +} diff --git a/test/Microsoft.DotNet.ProjectJsonMigration.Tests/TemporaryProjectFileRuleRunner.cs b/test/Microsoft.DotNet.ProjectJsonMigration.Tests/TemporaryProjectFileRuleRunner.cs new file mode 100644 index 000000000..a89257594 --- /dev/null +++ b/test/Microsoft.DotNet.ProjectJsonMigration.Tests/TemporaryProjectFileRuleRunner.cs @@ -0,0 +1,43 @@ +using System.Collections.Generic; +using Microsoft.Build.Construction; +using Microsoft.DotNet.ProjectModel; +using NuGet.Frameworks; + +namespace Microsoft.DotNet.ProjectJsonMigration.Tests +{ + public class TemporaryProjectFileRuleRunner + { + public static ProjectRootElement RunRules(IEnumerable rules, string projectJson, + string testDirectory) + { + var projectContext = GenerateProjectContextFromString(testDirectory, projectJson); + return RunMigrationRulesOnGeneratedProject(rules, projectContext, testDirectory); + } + + private static ProjectContext GenerateProjectContextFromString(string projectDirectory, string json) + { + var testPj = new ProjectJsonBuilder(null) + .FromStringBase(json) + .SaveToDisk(projectDirectory); + + return ProjectContext.Create(testPj, FrameworkConstants.CommonFrameworks.NetCoreApp10); + } + + private static ProjectRootElement RunMigrationRulesOnGeneratedProject(IEnumerable rules, + ProjectContext projectContext, string testDirectory) + { + var project = ProjectRootElement.Create(); + var testSettings = new MigrationSettings(testDirectory, testDirectory, "1.0.0", project); + var testInputs = new MigrationRuleInputs(new[] {projectContext}, project, + project.AddItemGroup(), + project.AddPropertyGroup()); + + foreach (var rule in rules) + { + rule.Apply(testSettings, testInputs); + } + + return project; + } + } +} \ No newline at end of file diff --git a/test/Microsoft.DotNet.ProjectJsonMigration.Tests/Transforms/GivenAConditionalTransform.cs b/test/Microsoft.DotNet.ProjectJsonMigration.Tests/Transforms/GivenAConditionalTransform.cs new file mode 100644 index 000000000..a36c23817 --- /dev/null +++ b/test/Microsoft.DotNet.ProjectJsonMigration.Tests/Transforms/GivenAConditionalTransform.cs @@ -0,0 +1,44 @@ +using Microsoft.DotNet.ProjectJsonMigration; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Xunit; +using FluentAssertions; +using Microsoft.Build.Construction; + +namespace Microsoft.DotNet.ProjectJsonMigration.Tests +{ + public class GivenAConditionalTransform + { + [Fact] + public void It_returns_null_when_condition_is_false() + { + var conditionalTransform = new TestConditionalTransform(t => false); + conditionalTransform.Transform("astring").Should().BeNull(); + } + + [Fact] + public void It_returns_result_of_ConditionallyTransform_when_condition_is_true() + { + var conditionalTransform = new TestConditionalTransform(t => true); + + var property = conditionalTransform.Transform("astring"); + property.Should().NotBeNull(); + property.Name.Should().Be("astring"); + property.Value.Should().Be("astring"); + } + + private class TestConditionalTransform : ConditionalTransform + { + public TestConditionalTransform(Func condition) : base(condition) { } + + public override ProjectPropertyElement ConditionallyTransform(string source) + { + var property = ProjectRootElement.Create().CreatePropertyElement(source); + property.Value = source; + return property; + } + } + } +} diff --git a/test/Microsoft.DotNet.ProjectJsonMigration.Tests/Transforms/GivenATransformApplicator.cs b/test/Microsoft.DotNet.ProjectJsonMigration.Tests/Transforms/GivenATransformApplicator.cs new file mode 100644 index 000000000..cc348a2ad --- /dev/null +++ b/test/Microsoft.DotNet.ProjectJsonMigration.Tests/Transforms/GivenATransformApplicator.cs @@ -0,0 +1,89 @@ +using System; +using Microsoft.Build.Construction; +using Xunit; +using Xunit.Runner.DotNet; +using FluentAssertions; +using System.Linq; + +namespace Microsoft.DotNet.ProjectJsonMigration.Tests.Transforms +{ + public class GivenATransformApplicator + { + [Fact] + public void It_merges_Metadata_and_Exclude_with_items_with_same_ItemType_and_Include_when_mergeExisting_is_true() + { + var metadata = new ItemMetadataValue[] + { + new ItemMetadataValue("metadata1", "value1"), + new ItemMetadataValue("metadata2", "value2") + }; + + var fullItemTransformSetIncludeValue = "include1;include2"; + + var transform1 = new AddItemTransform("item", + fullItemTransformSetIncludeValue, + "exclude1", + t => true, + mergeExisting: true) + .WithMetadata(metadata[0]); + + var transform2 = new AddItemTransform("item", + fullItemTransformSetIncludeValue, + "exclude2", + t => true, + mergeExisting: true) + .WithMetadata(metadata[1]); + + var mockProj = ProjectRootElement.Create(); + var itemGroup = mockProj.AddItemGroup(); + + var item1 = transform1.Transform("_"); + item1.AddMetadata(metadata[0].MetadataName, metadata[0].GetMetadataValue(null)); + + var item2 = transform2.Transform("_"); + item2.AddMetadata(metadata[1].MetadataName, metadata[1].GetMetadataValue(null)); + + var transformApplicator = new TransformApplicator(); + transformApplicator.Execute(new ProjectItemElement[] {item1, item2}, itemGroup, mergeExisting:true); + + itemGroup.Items.Count.Should().Be(1); + + var item = itemGroup.Items.First(); + item.Exclude.Should().Be("exclude1;exclude2"); + + item.Metadata.Count().Should().Be(2); + var foundMetadata = metadata.ToDictionary, string, bool>(m => m.MetadataName, + m => false); + + foreach (var metadataEntry in item.Metadata) + { + foundMetadata.Should().ContainKey(metadataEntry.Name); + foundMetadata[metadataEntry.Name].Should().BeFalse(); + foundMetadata[metadataEntry.Name] = true; + } + + foundMetadata.All(kv => kv.Value).Should().BeTrue(); + } + +// [Fact] +// public void It_adds_duplicate_properties_to_the_project_with_specified_value_when_the_property_exists() +// { +// var mockProj = ProjectRootElement.Create(); +// var propertyGroup = mockProj.AddPropertyGroup(); +// var propertyName = "Property1"; +// var propertyValue = "Value1"; +// +// var propertyTransform = new AddPropertyTransform(propertyName, propertyValue, t => true); +// propertyTransform.Transform("_"); +// propertyTransform.Transform("_", mockProj, propertyGroup); +// +// propertyGroup.Properties.Count.Should().Be(2); +// +// foreach (var property in propertyGroup.Properties) +// { +// property.Name.Should().Be(propertyName); +// property.Value.Should().Be(propertyValue); +// } +// } + } +} \ No newline at end of file diff --git a/test/Microsoft.DotNet.ProjectJsonMigration.Tests/Transforms/GivenAnAddBoolPropertyTransform.cs b/test/Microsoft.DotNet.ProjectJsonMigration.Tests/Transforms/GivenAnAddBoolPropertyTransform.cs new file mode 100644 index 000000000..9d30d4284 --- /dev/null +++ b/test/Microsoft.DotNet.ProjectJsonMigration.Tests/Transforms/GivenAnAddBoolPropertyTransform.cs @@ -0,0 +1,39 @@ +using Microsoft.Build.Construction; +using Microsoft.DotNet.ProjectJsonMigration; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Xunit; +using FluentAssertions; + +namespace Microsoft.DotNet.Migration.Tests +{ + public class GivenAnAddBoolPropertyTransform + { + [Theory] + [InlineData(true)] + [InlineData(false)] + public void It_returns_a_property_to_the_project_with_boolean_value(bool propertyValue) + { + var propertyName = "Property1"; + + var propertyTransform = new AddBoolPropertyTransform(propertyName, t => true); + var property = propertyTransform.Transform(propertyValue); + + property.Name.Should().Be(propertyName); + property.Value.Should().Be(propertyValue.ToString()); + } + + [Theory] + [InlineData(true)] + [InlineData(false)] + public void It_returns_null_when_condition_is_false(bool propertyValue) + { + var propertyName = "Property1"; + + var propertyTransform = new AddBoolPropertyTransform(propertyName, t => false); + propertyTransform.Transform(propertyValue).Should().BeNull(); + } + } +} diff --git a/test/Microsoft.DotNet.ProjectJsonMigration.Tests/Transforms/GivenAnAddItemTransform.cs b/test/Microsoft.DotNet.ProjectJsonMigration.Tests/Transforms/GivenAnAddItemTransform.cs new file mode 100644 index 000000000..0ea9c4cef --- /dev/null +++ b/test/Microsoft.DotNet.ProjectJsonMigration.Tests/Transforms/GivenAnAddItemTransform.cs @@ -0,0 +1,84 @@ +using Microsoft.Build.Construction; +using Microsoft.DotNet.ProjectJsonMigration; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Xunit; +using FluentAssertions; + +namespace Microsoft.DotNet.Migration.Tests +{ + public class GivenAnAddItemTransform + { + [Fact] + public void It_returns_an_item_with_Include_Exclude_and_Metadata_to_project_when_condition_is_true() + { + var itemTransforms = GetFullItemTransformSet(true); + + foreach (var transform in itemTransforms) + { + var item = transform.Transform("_"); + + item.Should().NotBeNull(); + item.Include.Should().Be(FullItemTransformSetIncludeValue); + item.Exclude.Should().Be(FullItemTransformSetExcludeValue); + + item.HasMetadata.Should().BeTrue(); + + var metadata = item.Metadata.First(); + metadata.Name.Should().Be(FullItemTransformSetMetadataName); + metadata.Value.Should().Be(FullItemTransformSetMetadataValue); + } + } + + [Fact] + public void It_returns_null_when_condition_is_false() + { + var itemTransforms = GetFullItemTransformSet(false); + + foreach (var transform in itemTransforms) + { + transform.Transform("_").Should().BeNull(); + } + } + + private static string FullItemTransformSetItemNamePrefix => "item"; + private static string FullItemTransformSetIncludeValue => "include1;include2"; + private static string FullItemTransformSetExcludeValue => "exclude1;exclude2"; + private static string FullItemTransformSetMetadataName => "SomeName"; + private static string FullItemTransformSetMetadataValue => "SomeValue"; + + private AddItemTransform[] GetFullItemTransformSet(bool condition) + { + return new AddItemTransform[] + { + new AddItemTransform(FullItemTransformSetItemNamePrefix + "1", + FullItemTransformSetIncludeValue.Split(';'), + FullItemTransformSetExcludeValue.Split(';'), + t => condition) + .WithMetadata(FullItemTransformSetMetadataName, FullItemTransformSetMetadataValue), + new AddItemTransform(FullItemTransformSetItemNamePrefix + "2", + t => FullItemTransformSetIncludeValue, + t => FullItemTransformSetExcludeValue, + t => condition) + .WithMetadata(FullItemTransformSetMetadataName, t => FullItemTransformSetMetadataValue), + new AddItemTransform(FullItemTransformSetItemNamePrefix + "3", + FullItemTransformSetIncludeValue, + t => FullItemTransformSetExcludeValue, + t => condition) + .WithMetadata(new ItemMetadataValue(FullItemTransformSetMetadataName, FullItemTransformSetMetadataValue)), + new AddItemTransform(FullItemTransformSetItemNamePrefix + "4", + t => FullItemTransformSetIncludeValue, + FullItemTransformSetExcludeValue, + t => condition) + .WithMetadata(new ItemMetadataValue(FullItemTransformSetMetadataName, t => FullItemTransformSetMetadataValue)), + new AddItemTransform(FullItemTransformSetItemNamePrefix + "5", + FullItemTransformSetIncludeValue, + FullItemTransformSetExcludeValue, + t => condition) + .WithMetadata(FullItemTransformSetMetadataName, FullItemTransformSetMetadataValue) + }; + } + } +} diff --git a/test/Microsoft.DotNet.ProjectJsonMigration.Tests/Transforms/GivenAnAddPropertyTransform.cs b/test/Microsoft.DotNet.ProjectJsonMigration.Tests/Transforms/GivenAnAddPropertyTransform.cs new file mode 100644 index 000000000..6d497a522 --- /dev/null +++ b/test/Microsoft.DotNet.ProjectJsonMigration.Tests/Transforms/GivenAnAddPropertyTransform.cs @@ -0,0 +1,93 @@ +using Microsoft.Build.Construction; +using Microsoft.DotNet.ProjectJsonMigration; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Xunit; +using FluentAssertions; + +namespace Microsoft.DotNet.Migration.Tests +{ + public class GivenAnAddPropertyTransform + { + [Fact] + public void It_returns_a_property_with_specified_value() + { + var propertyName = "Property1"; + var propertyValue = "Value1"; + + var propertyTransform = new AddPropertyTransform(propertyName, propertyValue, t=>true); + var property = propertyTransform.Transform("_"); + + property.Name.Should().Be(propertyName); + property.Value.Should().Be(propertyValue); + } + + [Fact] + public void It_returns_a_property_with_computed_value() + { + var propertyName = "Property1"; + var propertyValue = "Value1"; + + var propertyTransform = new AddPropertyTransform(propertyName, t => t.ToUpper(), t => true); + var property = propertyTransform.Transform(propertyValue); + + property.Name.Should().Be(propertyName); + property.Value.Should().Be(propertyValue.ToUpper()); + } + + [Fact] + public void It_returns_null_when_condition_is_false() + { + var propertyName = "Property1"; + var propertyValue = "Value1"; + + var propertyTransform = new AddPropertyTransform(propertyName, propertyValue, t => false); + propertyTransform.Transform(propertyValue).Should().BeNull(); + } + + [Fact] + public void It_returns_a_property_when_source_is_null_and_propertyValue_is_a_string() + { + var propertyName = "Property1"; + var propertyValue = "Value1"; + + var propertyTransform = new AddPropertyTransform( + propertyName, + propertyValue, + t => true); + var property = propertyTransform.Transform(null); + property.Should().NotBeNull(); + property.Value.Should().Be(propertyValue); + } + + [Fact] + public void It_returns_a_property_when_source_is_null_and_propertyValue_is_a_Func_that_handles_null() + { + var propertyName = "Property1"; + var propertyValue = "Value1"; + + var propertyTransform = new AddPropertyTransform( + propertyName, + t=> t == null ? propertyValue.ToUpper() : propertyValue.ToLower(), + t => true); + var property = propertyTransform.Transform(null); + property.Value.Should().Be(propertyValue.ToUpper()); + } + + [Fact] + public void It_throws_when_source_is_null_and_propertyValue_is_a_Func_that_doesnt_handle_null() + { + var propertyName = "Property1"; + + var propertyTransform = new AddPropertyTransform( + propertyName, + t => t.ToUpper(), + t => true); + + Action transform = () => propertyTransform.Transform(null); + transform.ShouldThrow(); + } + } +} diff --git a/test/Microsoft.DotNet.ProjectJsonMigration.Tests/Transforms/GivenAnAddStringPropertyTransform.cs b/test/Microsoft.DotNet.ProjectJsonMigration.Tests/Transforms/GivenAnAddStringPropertyTransform.cs new file mode 100644 index 000000000..17784f1c8 --- /dev/null +++ b/test/Microsoft.DotNet.ProjectJsonMigration.Tests/Transforms/GivenAnAddStringPropertyTransform.cs @@ -0,0 +1,57 @@ +using Microsoft.Build.Construction; +using Microsoft.DotNet.ProjectJsonMigration; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Xunit; +using FluentAssertions; + +namespace Microsoft.DotNet.Migration.Tests +{ + public class GivenAnAddStringPropertyTransform + { + [Fact] + public void It_adds_a_property_to_the_project_with_string_value() + { + var propertyName = "Property1"; + var propertyValue = "TestValue1"; + + var propertyTransform = new AddStringPropertyTransform(propertyName, t => true); + var property = propertyTransform.Transform(propertyValue); + property.Name.Should().Be(propertyName); + property.Value.Should().Be(propertyValue); + } + + [Fact] + public void It_returns_null_when_propertyValue_is_null_and_condition_is_passed() + { + var propertyName = "Property1"; + string propertyValue = null; + + var propertyTransform = new AddStringPropertyTransform(propertyName, t => true); + propertyTransform.Transform(propertyValue).Should().BeNull(); + } + [Fact] + public void It_returns_null_when_propertyValue_is_null_and_condition_is_not_passed() + { + var mockProj = ProjectRootElement.Create(); + var propertyName = "Property1"; + string propertyValue = null; + + var propertyTransform = new AddStringPropertyTransform(propertyName); + propertyTransform.Transform(propertyValue).Should().BeNull(); + } + + + [Fact] + public void It_returns_null_when_condition_is_false() + { + var propertyName = "Property1"; + var propertyValue = "TestValue1"; + + var propertyTransform = new AddStringPropertyTransform(propertyName, t => false); + propertyTransform.Transform(propertyValue).Should().BeNull(); + } + } +} diff --git a/test/Microsoft.DotNet.ProjectJsonMigration.Tests/Transforms/GivenAnIncludeContextTransformation.cs b/test/Microsoft.DotNet.ProjectJsonMigration.Tests/Transforms/GivenAnIncludeContextTransformation.cs new file mode 100644 index 000000000..f15b0036c --- /dev/null +++ b/test/Microsoft.DotNet.ProjectJsonMigration.Tests/Transforms/GivenAnIncludeContextTransformation.cs @@ -0,0 +1,11 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; + +namespace Microsoft.DotNet.Migration.Tests +{ + public class GivenAnIncludeContextTransformation + { + } +} diff --git a/test/Microsoft.DotNet.ProjectJsonMigration.Tests/msbuild.exe.config b/test/Microsoft.DotNet.ProjectJsonMigration.Tests/msbuild.exe.config new file mode 100644 index 000000000..2b31011cf --- /dev/null +++ b/test/Microsoft.DotNet.ProjectJsonMigration.Tests/msbuild.exe.config @@ -0,0 +1 @@ +hey \ No newline at end of file diff --git a/test/Microsoft.DotNet.ProjectJsonMigration.Tests/project.json b/test/Microsoft.DotNet.ProjectJsonMigration.Tests/project.json new file mode 100644 index 000000000..e1eef96cb --- /dev/null +++ b/test/Microsoft.DotNet.ProjectJsonMigration.Tests/project.json @@ -0,0 +1,39 @@ +{ + "version": "1.0.0-*", + "buildOptions": { + "copyToOutput": ["MSBuild.exe", "msbuild.exe.config"] + }, + "dependencies": { + "Microsoft.NETCore.App": { + "type": "platform", + "version": "1.0.0" + }, + "Microsoft.DotNet.ProjectJsonMigration": { + "target": "project" + }, + "xunit": "2.2.0-beta3-build3330", + "dotnet-test-xunit": "1.0.0-rc2-318883-21", + "FluentAssertions": "4.0.0", + "moq.netcore": "4.4.0-beta8", + "Microsoft.DotNet.Tools.Tests.Utilities": { + "target": "project" + }, + "Microsoft.DotNet.Cli.Utils": { + "target": "project" + }, + "dotnet": { + "target":"project" + } + }, + + "frameworks": { + "netcoreapp1.0": { + "imports": [ + "netstandardapp1.5", + "dotnet5.4", + "portable-net451+win8" + ] + } + }, + "testRunner": "xunit" +} From 903764aa7dab4f20cc48b38f5f8a3d47b058900c Mon Sep 17 00:00:00 2001 From: Bryan Thornbury Date: Mon, 22 Aug 2016 13:39:00 -0700 Subject: [PATCH 7/8] Fix msbuildexe config --- .../{msbuild.exe.config => MSBuild.exe.config} | 0 test/Microsoft.DotNet.ProjectJsonMigration.Tests/project.json | 2 +- 2 files changed, 1 insertion(+), 1 deletion(-) rename test/Microsoft.DotNet.ProjectJsonMigration.Tests/{msbuild.exe.config => MSBuild.exe.config} (100%) diff --git a/test/Microsoft.DotNet.ProjectJsonMigration.Tests/msbuild.exe.config b/test/Microsoft.DotNet.ProjectJsonMigration.Tests/MSBuild.exe.config similarity index 100% rename from test/Microsoft.DotNet.ProjectJsonMigration.Tests/msbuild.exe.config rename to test/Microsoft.DotNet.ProjectJsonMigration.Tests/MSBuild.exe.config diff --git a/test/Microsoft.DotNet.ProjectJsonMigration.Tests/project.json b/test/Microsoft.DotNet.ProjectJsonMigration.Tests/project.json index e1eef96cb..a7b61f903 100644 --- a/test/Microsoft.DotNet.ProjectJsonMigration.Tests/project.json +++ b/test/Microsoft.DotNet.ProjectJsonMigration.Tests/project.json @@ -1,7 +1,7 @@ { "version": "1.0.0-*", "buildOptions": { - "copyToOutput": ["MSBuild.exe", "msbuild.exe.config"] + "copyToOutput": ["MSBuild.exe", "MSBuild.exe.config"] }, "dependencies": { "Microsoft.NETCore.App": { From b0554d3ff3b60205d01fe6d5a09356cd90c069f3 Mon Sep 17 00:00:00 2001 From: Bryan Thornbury Date: Tue, 23 Aug 2016 13:50:05 -0700 Subject: [PATCH 8/8] PR Feedback --- Microsoft.DotNet.Cli.sln | 11 +- .../Program.cs | 0 .../echoscript.cmd | 0 .../echoscript.sh | 0 .../project.json | 0 .../ConstantPackageNames.cs | 17 ++ .../DefaultMigrationRuleSet.cs | 3 +- .../MSBuildExtensions.cs | 41 ++-- ...icrosoft.DotNet.ProjectJsonMigration.xproj | 2 +- .../MigrationError.cs | 30 +++ .../MigrationErrorCodes.cs | 35 ++++ .../MigrationRuleInputs.cs | 4 +- .../MigrationSettings.cs | 23 --- .../MigrationTrace.cs | 54 +++--- .../Models/ItemMetadataValue.cs | 13 +- .../Models/ProjectProperty.cs | 14 -- .../ProjectMigrator.cs | 101 +++++++--- .../Properties/AssemblyInfo.cs | 5 +- .../Rules/IMigrationRule.cs | 13 +- .../Rules/MigrateBuildOptionsRule.cs | 45 ++--- .../Rules/MigrateConfigurationsRule.cs | 34 ++-- .../Rules/MigrateProjectDependenciesRule.cs | 57 +++--- .../Rules/MigratePublishOptionsRule.cs | 27 +-- .../Rules/MigrateRootOptionsRule.cs | 15 +- .../Rules/MigrateRuntimeOptionsRule.cs | 14 +- .../Rules/MigrateScriptsRule.cs | 43 ++--- .../Rules/MigrateTFMRule.cs | 25 +-- .../MigrateXprojProjectReferencesRule.cs | 8 +- .../Rules/SaveOutputProjectRule.cs | 11 +- .../Rules/TemporaryMutateProjectJsonRule.cs | 23 +-- .../project.json | 8 - .../transforms/AddBoolPropertyTransform.cs | 25 --- .../transforms/AddItemTransform.cs | 28 ++- .../transforms/AddPropertyTransform.cs | 2 +- .../transforms/AddStringPropertyTransform.cs | 25 --- .../transforms/ConditionalTransform.cs | 2 +- .../transforms/ITransform.cs | 2 +- .../transforms/ITransformApplicator.cs | 2 +- .../transforms/IncludeContextTransform.cs | 13 +- .../transforms/TransformApplicator.cs | 12 +- src/dotnet/Properties/AssemblyInfo.cs | 3 +- .../commands/dotnet-migrate/MigrateCommand.cs | 36 ++-- src/dotnet/commands/dotnet-migrate/Program.cs | 5 +- .../dotnet-migrate/ProjectJsonParser.cs | 17 +- .../TemporaryDotnetNewTemplateProject.cs | 35 ++-- .../GivenAProjectMigrator.cs | 90 +++++++++ .../GivenMSBuildExtensions.cs | 27 ++- .../MSBuild.exe | 1 + .../MSBuild.exe.config | 2 +- ...ft.DotNet.ProjectJsonMigration.Tests.xproj | 6 +- .../ProjectJsonBuilder.cs | 9 +- .../GivenThatIWantToMigrateBuildOptions.cs | 13 +- .../GivenThatIWantToMigrateConfigurations.cs | 11 +- ...enThatIWantToMigrateProjectDependencies.cs | 5 +- .../GivenThatIWantToMigratePublishOptions.cs | 7 +- .../GivenThatIWantToMigrateRuntimeOptions.cs | 1 + .../Rules/GivenThatIWantToMigrateScripts.cs | 11 +- .../Rules/GivenThatIWantToMigrateTFMs.cs | 3 +- .../TemporaryProjectFileRuleRunner.cs | 1 + .../Transforms/GivenAConditionalTransform.cs | 1 + .../Transforms/GivenATransformApplicator.cs | 29 +-- .../GivenAnAddBoolPropertyTransform.cs | 39 ---- .../Transforms/GivenAnAddItemTransform.cs | 2 + .../Transforms/GivenAnAddPropertyTransform.cs | 1 + .../GivenAnAddStringPropertyTransform.cs | 57 ------ .../GivenAnIncludeContextTransformation.cs | 1 + .../Commands/NewCommand.cs | 6 + .../GivenThatIWantToMigrateTestApps.cs | 181 +++++++++++------- test/dotnet-migrate.Tests/MSBuild.exe | 1 + test/dotnet-migrate.Tests/MSBuild.exe.config | 1 + .../dotnet-migrate.Tests.xproj | 21 ++ test/dotnet-migrate.Tests/project.json | 39 ++++ 72 files changed, 769 insertions(+), 680 deletions(-) rename TestAssets/TestProjects/{TestAppWithMigrateAbleScripts => TestAppWithMigrateableScripts}/Program.cs (100%) rename TestAssets/TestProjects/{TestAppWithMigrateAbleScripts => TestAppWithMigrateableScripts}/echoscript.cmd (100%) rename TestAssets/TestProjects/{TestAppWithMigrateAbleScripts => TestAppWithMigrateableScripts}/echoscript.sh (100%) rename TestAssets/TestProjects/{TestAppWithMigrateAbleScripts => TestAppWithMigrateableScripts}/project.json (100%) create mode 100644 src/Microsoft.DotNet.ProjectJsonMigration/ConstantPackageNames.cs create mode 100644 src/Microsoft.DotNet.ProjectJsonMigration/MigrationError.cs create mode 100644 src/Microsoft.DotNet.ProjectJsonMigration/MigrationErrorCodes.cs delete mode 100644 src/Microsoft.DotNet.ProjectJsonMigration/Models/ProjectProperty.cs delete mode 100644 src/Microsoft.DotNet.ProjectJsonMigration/transforms/AddBoolPropertyTransform.cs delete mode 100644 src/Microsoft.DotNet.ProjectJsonMigration/transforms/AddStringPropertyTransform.cs create mode 100644 test/Microsoft.DotNet.ProjectJsonMigration.Tests/GivenAProjectMigrator.cs delete mode 100644 test/Microsoft.DotNet.ProjectJsonMigration.Tests/Transforms/GivenAnAddBoolPropertyTransform.cs delete mode 100644 test/Microsoft.DotNet.ProjectJsonMigration.Tests/Transforms/GivenAnAddStringPropertyTransform.cs rename test/{Microsoft.DotNet.ProjectJsonMigration.Tests/Scenarios => dotnet-migrate.Tests}/GivenThatIWantToMigrateTestApps.cs (52%) create mode 100644 test/dotnet-migrate.Tests/MSBuild.exe create mode 100644 test/dotnet-migrate.Tests/MSBuild.exe.config create mode 100644 test/dotnet-migrate.Tests/dotnet-migrate.Tests.xproj create mode 100644 test/dotnet-migrate.Tests/project.json diff --git a/Microsoft.DotNet.Cli.sln b/Microsoft.DotNet.Cli.sln index 6adc5857c..319430b98 100644 --- a/Microsoft.DotNet.Cli.sln +++ b/Microsoft.DotNet.Cli.sln @@ -1,7 +1,7 @@  Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio 14 -VisualStudioVersion = 14.0.25420.1 +VisualStudioVersion = 14.0.25123.0 MinimumVisualStudioVersion = 10.0.40219.1 Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{ED2FE3E2-F7E7-4389-8231-B65123F2076F}" EndProject @@ -104,8 +104,6 @@ Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "update-dependencies", "buil EndProject Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "Microsoft.DotNet.ProjectModel.Loader", "src\Microsoft.DotNet.ProjectModel.Loader\Microsoft.DotNet.ProjectModel.Loader.xproj", "{1C599FFD-FB52-4279-A8E5-465D3EC499E1}" EndProject -Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "Microsoft.DotNet.ProjectModel.Loader.Tests", "test\Microsoft.DotNet.ProjectModel.Loader.Tests\Microsoft.DotNet.ProjectModel.Loader.Tests.xproj", "{5DF6C9DA-6909-4EC0-909E-6913580BB4A4}" -EndProject Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "Microsoft.DotNet.Configurer", "src\Microsoft.DotNet.Configurer\Microsoft.DotNet.Configurer.xproj", "{E5ED47EF-BF25-4DA9-A7FE-290C642CBF0F}" EndProject Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "Microsoft.DotNet.Configurer.UnitTests", "test\Microsoft.DotNet.Configurer.UnitTests\Microsoft.DotNet.Configurer.UnitTests.xproj", "{4C3B06D5-B6D5-4E5B-A44F-3EBE52A1C759}" @@ -156,6 +154,12 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "publish", "publish", "{27B1 build\publish\PublishContent.targets = build\publish\PublishContent.targets EndProjectSection EndProject +Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "Microsoft.DotNet.ProjectJsonMigration", "src\Microsoft.DotNet.ProjectJsonMigration\Microsoft.DotNet.ProjectJsonMigration.xproj", "{0E083818-2320-4388-8007-4F720FD5C634}" +EndProject +Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "Microsoft.DotNet.ProjectJsonMigration.Tests", "test\Microsoft.DotNet.ProjectJsonMigration.Tests\Microsoft.DotNet.ProjectJsonMigration.Tests.xproj", "{1F2EF070-AC5F-4078-AFB0-65745AC691B9}" +EndProject +Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "dotnet-migrate.Tests", "test\dotnet-migrate.Tests\dotnet-migrate.Tests.xproj", "{1F2EF070-AC5F-4078-AFB0-65745AC691B9}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -931,6 +935,7 @@ Global {A16958E1-24C7-4F1E-B317-204AD91625DD} = {ED2FE3E2-F7E7-4389-8231-B65123F2076F} {BD7833F8-3209-4682-BF75-B4BCA883E279} = {ED2FE3E2-F7E7-4389-8231-B65123F2076F} {DCDFE282-03DE-4DBC-B90C-CC3CE3EC8162} = {ED2FE3E2-F7E7-4389-8231-B65123F2076F} + {688870C8-9843-4F9E-8576-D39290AD0F25} = {ED2FE3E2-F7E7-4389-8231-B65123F2076F} {65741CB1-8AEE-4C66-8198-10A7EA0E4258} = {17735A9D-BFD9-4585-A7CB-3208CA6EA8A7} {713CBFBB-5392-438D-B766-A9A585EF1BB8} = {17735A9D-BFD9-4585-A7CB-3208CA6EA8A7} {60CF7E6C-D6C8-439D-B7B7-D8A27E29BE2C} = {ED2FE3E2-F7E7-4389-8231-B65123F2076F} diff --git a/TestAssets/TestProjects/TestAppWithMigrateAbleScripts/Program.cs b/TestAssets/TestProjects/TestAppWithMigrateableScripts/Program.cs similarity index 100% rename from TestAssets/TestProjects/TestAppWithMigrateAbleScripts/Program.cs rename to TestAssets/TestProjects/TestAppWithMigrateableScripts/Program.cs diff --git a/TestAssets/TestProjects/TestAppWithMigrateAbleScripts/echoscript.cmd b/TestAssets/TestProjects/TestAppWithMigrateableScripts/echoscript.cmd similarity index 100% rename from TestAssets/TestProjects/TestAppWithMigrateAbleScripts/echoscript.cmd rename to TestAssets/TestProjects/TestAppWithMigrateableScripts/echoscript.cmd diff --git a/TestAssets/TestProjects/TestAppWithMigrateAbleScripts/echoscript.sh b/TestAssets/TestProjects/TestAppWithMigrateableScripts/echoscript.sh similarity index 100% rename from TestAssets/TestProjects/TestAppWithMigrateAbleScripts/echoscript.sh rename to TestAssets/TestProjects/TestAppWithMigrateableScripts/echoscript.sh diff --git a/TestAssets/TestProjects/TestAppWithMigrateAbleScripts/project.json b/TestAssets/TestProjects/TestAppWithMigrateableScripts/project.json similarity index 100% rename from TestAssets/TestProjects/TestAppWithMigrateAbleScripts/project.json rename to TestAssets/TestProjects/TestAppWithMigrateableScripts/project.json diff --git a/src/Microsoft.DotNet.ProjectJsonMigration/ConstantPackageNames.cs b/src/Microsoft.DotNet.ProjectJsonMigration/ConstantPackageNames.cs new file mode 100644 index 000000000..b2d1d0777 --- /dev/null +++ b/src/Microsoft.DotNet.ProjectJsonMigration/ConstantPackageNames.cs @@ -0,0 +1,17 @@ +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 +{ + public class ConstantPackageNames + { + public const string CSdkPackageName = "Microsoft.DotNet.Core.Sdk"; + } +} \ No newline at end of file diff --git a/src/Microsoft.DotNet.ProjectJsonMigration/DefaultMigrationRuleSet.cs b/src/Microsoft.DotNet.ProjectJsonMigration/DefaultMigrationRuleSet.cs index be900ff02..7801a11d6 100644 --- a/src/Microsoft.DotNet.ProjectJsonMigration/DefaultMigrationRuleSet.cs +++ b/src/Microsoft.DotNet.ProjectJsonMigration/DefaultMigrationRuleSet.cs @@ -9,6 +9,7 @@ using Microsoft.DotNet.ProjectModel; using Microsoft.DotNet.Cli; using System.Linq; using System.IO; +using Microsoft.DotNet.ProjectJsonMigration.Rules; using Newtonsoft.Json; namespace Microsoft.DotNet.ProjectJsonMigration @@ -33,7 +34,7 @@ namespace Microsoft.DotNet.ProjectJsonMigration { foreach (var rule in Rules) { - MigrationTrace.Instance.WriteLine($"{nameof(DefaultMigrationRuleSet)}: Executing migration rule {nameof(rule)}"); + MigrationTrace.Instance.WriteLine($"{nameof(DefaultMigrationRuleSet)}: Executing migration rule {rule.GetType().Name}"); rule.Apply(migrationSettings, migrationRuleInputs); } } diff --git a/src/Microsoft.DotNet.ProjectJsonMigration/MSBuildExtensions.cs b/src/Microsoft.DotNet.ProjectJsonMigration/MSBuildExtensions.cs index a25306535..241092ec5 100644 --- a/src/Microsoft.DotNet.ProjectJsonMigration/MSBuildExtensions.cs +++ b/src/Microsoft.DotNet.ProjectJsonMigration/MSBuildExtensions.cs @@ -18,29 +18,25 @@ namespace Microsoft.DotNet.ProjectJsonMigration public static IEnumerable PropertiesWithoutConditions( this ProjectRootElement projectRoot) { - return projectRoot.Properties - .Where(p => p.Condition == string.Empty - && p.AllParents.Count(parent => parent.Condition != string.Empty) == 0); + return ElementsWithoutConditions(projectRoot.Properties); } public static IEnumerable ItemsWithoutConditions( this ProjectRootElement projectRoot) { - return projectRoot.Items - .Where(p => string.IsNullOrEmpty(p.Condition) - && p.AllParents.All(parent => string.IsNullOrEmpty(parent.Condition))); + return ElementsWithoutConditions(projectRoot.Items); } public static IEnumerable Includes( this ProjectItemElement item) { - return item.Include.Equals(string.Empty) ? Enumerable.Empty() : item.Include.Split(';'); + return SplitSemicolonDelimitedValues(item.Include); } public static IEnumerable Excludes( this ProjectItemElement item) { - return item.Exclude.Equals(string.Empty) ? Enumerable.Empty() : item.Exclude.Split(';'); + return SplitSemicolonDelimitedValues(item.Exclude); } public static IEnumerable AllConditions(this ProjectElement projectElement) @@ -48,7 +44,7 @@ namespace Microsoft.DotNet.ProjectJsonMigration return new string[] { projectElement.Condition }.Concat(projectElement.AllParents.Select(p=> p.Condition)); } - public static IEnumerable CommonIncludes(this ProjectItemElement item, ProjectItemElement otherItem) + public static IEnumerable IntersectIncludes(this ProjectItemElement item, ProjectItemElement otherItem) { return item.Includes().Intersect(otherItem.Includes()); } @@ -58,14 +54,14 @@ namespace Microsoft.DotNet.ProjectJsonMigration item.Include = string.Join(";", item.Includes().Except(includesToRemove)); } - public static void AddIncludes(this ProjectItemElement item, IEnumerable includes) + public static void UnionIncludes(this ProjectItemElement item, IEnumerable includesToAdd) { - item.Include = string.Join(";", item.Includes().Union(includes)); + item.Include = string.Join(";", item.Includes().Union(includesToAdd)); } - public static void AddExcludes(this ProjectItemElement item, IEnumerable excludes) + public static void UnionExcludes(this ProjectItemElement item, IEnumerable excludesToAdd) { - item.Exclude = string.Join(";", item.Excludes().Union(excludes)); + item.Exclude = string.Join(";", item.Excludes().Union(excludesToAdd)); } public static ProjectMetadataElement GetMetadataWithName(this ProjectItemElement item, string name) @@ -78,9 +74,9 @@ namespace Microsoft.DotNet.ProjectJsonMigration return metadata.Value.Equals(otherMetadata.Value, StringComparison.Ordinal); } - public static void AddMetadata(this ProjectItemElement item, ICollection metadatas) + public static void AddMetadata(this ProjectItemElement item, ICollection metadataElements) { - foreach (var metadata in metadatas) + foreach (var metadata in metadataElements) { item.AddMetadata(metadata); } @@ -103,12 +99,23 @@ namespace Microsoft.DotNet.ProjectJsonMigration throw new Exception("Cannot merge metadata with the same name and different values"); } - if (existingMetadata == null) + if (existingMetadata == default(ProjectMetadataElement)) { - Console.WriteLine(metadata.Name); + MigrationTrace.Instance.WriteLine($"{nameof(AddMetadata)}: Adding metadata to {item.ItemType} item: {{ {metadata.Name}, {metadata.Value} }}"); item.AddMetadata(metadata.Name, metadata.Value); } } + private static IEnumerable SplitSemicolonDelimitedValues(string combinedValue) + { + return string.IsNullOrEmpty(combinedValue) ? Enumerable.Empty() : combinedValue.Split(';'); + } + + private static IEnumerable ElementsWithoutConditions(IEnumerable elements) where T : ProjectElement + { + return elements + .Where(e => string.IsNullOrEmpty(e.Condition) + && e.AllParents.All(parent => string.IsNullOrEmpty(parent.Condition))); + } } } diff --git a/src/Microsoft.DotNet.ProjectJsonMigration/Microsoft.DotNet.ProjectJsonMigration.xproj b/src/Microsoft.DotNet.ProjectJsonMigration/Microsoft.DotNet.ProjectJsonMigration.xproj index 0bc54ed18..095a39599 100644 --- a/src/Microsoft.DotNet.ProjectJsonMigration/Microsoft.DotNet.ProjectJsonMigration.xproj +++ b/src/Microsoft.DotNet.ProjectJsonMigration/Microsoft.DotNet.ProjectJsonMigration.xproj @@ -4,7 +4,7 @@ 14.0 $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) - + 0E083818-2320-4388-8007-4F720FD5C634 Microsoft.DotNet.ProjectJsonMigration diff --git a/src/Microsoft.DotNet.ProjectJsonMigration/MigrationError.cs b/src/Microsoft.DotNet.ProjectJsonMigration/MigrationError.cs new file mode 100644 index 000000000..39398640c --- /dev/null +++ b/src/Microsoft.DotNet.ProjectJsonMigration/MigrationError.cs @@ -0,0 +1,30 @@ +using System; + +namespace Microsoft.DotNet.ProjectJsonMigration +{ + public class MigrationError + { + public string ErrorCode { get; } + + public string GeneralErrorReason { get; } + + public string Message { get; } + + public MigrationError(string errorCode, string generalErrorReason, string message) + { + ErrorCode = errorCode; + GeneralErrorReason = generalErrorReason; + Message = message; + } + + public void Throw() + { + throw new Exception(GetFormattedErrorMessage()); + } + + public string GetFormattedErrorMessage() + { + return $"{ErrorCode}::{GeneralErrorReason}: {Message}"; + } + } +} \ No newline at end of file diff --git a/src/Microsoft.DotNet.ProjectJsonMigration/MigrationErrorCodes.cs b/src/Microsoft.DotNet.ProjectJsonMigration/MigrationErrorCodes.cs new file mode 100644 index 000000000..132d51ab2 --- /dev/null +++ b/src/Microsoft.DotNet.ProjectJsonMigration/MigrationErrorCodes.cs @@ -0,0 +1,35 @@ +using System; + +namespace Microsoft.DotNet.ProjectJsonMigration +{ + public static partial class MigrationErrorCodes + { + public static Func MIGRATE1011 + => (message) => new MigrationError(nameof(MIGRATE1011), "Deprecated Project", message); + + public static Func MIGRATE1012 + => (message) => new MigrationError(nameof(MIGRATE1012), "Project not Restored", message); + + public static Func MIGRATE1013 + => (message) => new MigrationError(nameof(MIGRATE1013), "No Project", message); + + public static Func MIGRATE1014 + => (message) => new MigrationError(nameof(MIGRATE1014), "Unresolved Dependency", message); + + public static Func MIGRATE1015 + => (message) => new MigrationError(nameof(MIGRATE1015), "File Overwrite", message); + + public static Func MIGRATE1016 + => (message) => new MigrationError(nameof(MIGRATE1016), "Unsupported Script Variable", message); + + // Potentially Temporary (Point in Time) Errors + public static Func MIGRATE20011 + => (message) => new MigrationError(nameof(MIGRATE20011), "Multi-TFM", message); + + public static Func MIGRATE20012 + => (message) => new MigrationError(nameof(MIGRATE20012), "Configuration Exclude", message); + + public static Func MIGRATE20013 + => (message) => new MigrationError(nameof(MIGRATE20013), "Non-Csharp App", message); + } +} \ No newline at end of file diff --git a/src/Microsoft.DotNet.ProjectJsonMigration/MigrationRuleInputs.cs b/src/Microsoft.DotNet.ProjectJsonMigration/MigrationRuleInputs.cs index 45dde00d0..177cfc1c3 100644 --- a/src/Microsoft.DotNet.ProjectJsonMigration/MigrationRuleInputs.cs +++ b/src/Microsoft.DotNet.ProjectJsonMigration/MigrationRuleInputs.cs @@ -33,12 +33,12 @@ namespace Microsoft.DotNet.ProjectJsonMigration public MigrationRuleInputs( IEnumerable projectContexts, - ProjectRootElement outputProject, + ProjectRootElement outputMSBuildProject, ProjectItemGroupElement commonItemGroup, ProjectPropertyGroupElement commonPropertyGroup) { ProjectContexts = projectContexts; - OutputMSBuildProject = outputProject; + OutputMSBuildProject = outputMSBuildProject; CommonItemGroup = commonItemGroup; CommonPropertyGroup = commonPropertyGroup; } diff --git a/src/Microsoft.DotNet.ProjectJsonMigration/MigrationSettings.cs b/src/Microsoft.DotNet.ProjectJsonMigration/MigrationSettings.cs index 10d7646ba..093980649 100644 --- a/src/Microsoft.DotNet.ProjectJsonMigration/MigrationSettings.cs +++ b/src/Microsoft.DotNet.ProjectJsonMigration/MigrationSettings.cs @@ -20,17 +20,6 @@ namespace Microsoft.DotNet.ProjectJsonMigration 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, @@ -42,17 +31,5 @@ namespace Microsoft.DotNet.ProjectJsonMigration SdkPackageVersion = sdkPackageVersion; MSBuildProjectTemplate = msBuildProjectTemplate; } - - public MigrationSettings( - string projectDirectory, - string outputDirectory, - string sdkPackageVersion, - string msbuildProjectTemplateFilePath) - { - ProjectDirectory = projectDirectory; - OutputDirectory = outputDirectory; - SdkPackageVersion = sdkPackageVersion; - MSBuildProjectTemplate = ProjectRootElement.Open(msbuildProjectTemplateFilePath); - } } } diff --git a/src/Microsoft.DotNet.ProjectJsonMigration/MigrationTrace.cs b/src/Microsoft.DotNet.ProjectJsonMigration/MigrationTrace.cs index 05cd7e7db..717ed0741 100644 --- a/src/Microsoft.DotNet.ProjectJsonMigration/MigrationTrace.cs +++ b/src/Microsoft.DotNet.ProjectJsonMigration/MigrationTrace.cs @@ -3,43 +3,35 @@ using System.Text.RegularExpressions; namespace Microsoft.DotNet.ProjectJsonMigration { - public class MigrationTrace - { - public static MigrationTrace Instance { get; set; } - - static MigrationTrace () + public class MigrationTrace { - Instance = new MigrationTrace(); - } + public static MigrationTrace Instance { get; set; } - public string EnableEnvironmentVariable => "DOTNET_MIGRATION_TRACE"; + static MigrationTrace () + { + Instance = new MigrationTrace(); + } - public bool IsEnabled - { - get - { + public string EnableEnvironmentVariable => "DOTNET_MIGRATION_TRACE"; + + public bool IsEnabled + { + get + { #if DEBUG - return true; + return true; #else - return Environment.GetEnvironmentVariable(EnableEnvironmentVariable) != null; + 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); + } + } } - - public void WriteLine(string message) - { - if (IsEnabled) - { - Console.WriteLine(message); - } - } - } } \ No newline at end of file diff --git a/src/Microsoft.DotNet.ProjectJsonMigration/Models/ItemMetadataValue.cs b/src/Microsoft.DotNet.ProjectJsonMigration/Models/ItemMetadataValue.cs index 536defd6d..1e257d65c 100644 --- a/src/Microsoft.DotNet.ProjectJsonMigration/Models/ItemMetadataValue.cs +++ b/src/Microsoft.DotNet.ProjectJsonMigration/Models/ItemMetadataValue.cs @@ -1,16 +1,15 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; +// Copyright (c) .NET Foundation and contributors. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. +using System; -namespace Microsoft.DotNet.ProjectJsonMigration +namespace Microsoft.DotNet.ProjectJsonMigration.Models { public class ItemMetadataValue { public string MetadataName { get; } - private string _metadataValue; - private Func _metadataValueFunc; + private readonly string _metadataValue; + private readonly Func _metadataValueFunc; public ItemMetadataValue(string metadataName, string metadataValue) { diff --git a/src/Microsoft.DotNet.ProjectJsonMigration/Models/ProjectProperty.cs b/src/Microsoft.DotNet.ProjectJsonMigration/Models/ProjectProperty.cs deleted file mode 100644 index 9b7ddc395..000000000 --- a/src/Microsoft.DotNet.ProjectJsonMigration/Models/ProjectProperty.cs +++ /dev/null @@ -1,14 +0,0 @@ -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; - } - } -} \ No newline at end of file diff --git a/src/Microsoft.DotNet.ProjectJsonMigration/ProjectMigrator.cs b/src/Microsoft.DotNet.ProjectJsonMigration/ProjectMigrator.cs index b89c9f03b..6877f4793 100644 --- a/src/Microsoft.DotNet.ProjectJsonMigration/ProjectMigrator.cs +++ b/src/Microsoft.DotNet.ProjectJsonMigration/ProjectMigrator.cs @@ -9,6 +9,8 @@ using Microsoft.DotNet.ProjectModel; using Microsoft.DotNet.Cli; using System.Linq; using System.IO; +using Microsoft.DotNet.ProjectJsonMigration.Rules; +using Microsoft.DotNet.Tools.Common; using Newtonsoft.Json.Linq; namespace Microsoft.DotNet.ProjectJsonMigration @@ -24,30 +26,34 @@ namespace Microsoft.DotNet.ProjectJsonMigration // - Migrating Deprecated project.jsons // - Configuration dependent source exclusion - public void Migrate(MigrationSettings migrationSettings) - { - var projectDirectory = migrationSettings.ProjectDirectory; - EnsureDirectoryExists(migrationSettings.OutputDirectory); + private readonly IMigrationRule _ruleSet; - var migrationRuleInputs = ComputeMigrationRuleInputs(migrationSettings); - VerifyInputs(migrationRuleInputs); - - new DefaultMigrationRuleSet().Apply(migrationSettings, migrationRuleInputs); + public ProjectMigrator() : this(new DefaultMigrationRuleSet()) { } + + public ProjectMigrator(IMigrationRule ruleSet) + { + _ruleSet = ruleSet; } - private void EnsureDirectoryExists(string outputDirectory) + public void Migrate(MigrationSettings migrationSettings) { - if (!Directory.Exists(outputDirectory)) - { - Directory.CreateDirectory(outputDirectory); - } + var migrationRuleInputs = ComputeMigrationRuleInputs(migrationSettings); + VerifyInputs(migrationRuleInputs, migrationSettings); + + SetupOutputDirectory(migrationSettings.ProjectDirectory, migrationSettings.OutputDirectory); + + _ruleSet.Apply(migrationSettings, migrationRuleInputs); } private MigrationRuleInputs ComputeMigrationRuleInputs(MigrationSettings migrationSettings) { var projectContexts = ProjectContext.CreateContextForEachFramework(migrationSettings.ProjectDirectory); - var templateMSBuildProject = migrationSettings.MSBuildProjectTemplate ?? ProjectRootElement.Create(); + var templateMSBuildProject = migrationSettings.MSBuildProjectTemplate; + if (templateMSBuildProject == null) + { + throw new Exception("Expected non-null MSBuildProjectTemplate in MigrationSettings"); + } var propertyGroup = templateMSBuildProject.AddPropertyGroup(); var itemGroup = templateMSBuildProject.AddItemGroup(); @@ -55,29 +61,80 @@ namespace Microsoft.DotNet.ProjectJsonMigration return new MigrationRuleInputs(projectContexts, templateMSBuildProject, itemGroup, propertyGroup); } - private void VerifyInputs(MigrationRuleInputs migrationRuleInputs) + private void VerifyInputs(MigrationRuleInputs migrationRuleInputs, MigrationSettings migrationSettings) { - VerifyProject(migrationRuleInputs.ProjectContexts); + VerifyProject(migrationRuleInputs.ProjectContexts, migrationSettings.ProjectDirectory); } - private void VerifyProject(IEnumerable projectContexts) + private void VerifyProject(IEnumerable projectContexts, string projectDirectory) { if (projectContexts.Count() > 1) { - throw new Exception("MultiTFM projects currently not supported."); + MigrationErrorCodes.MIGRATE20011($"Multi-TFM projects currently not supported.").Throw(); } - if (projectContexts.Count() == 0) + if (!projectContexts.Any()) { - throw new Exception("No projects found"); + MigrationErrorCodes.MIGRATE1013($"No projects found in {projectDirectory}").Throw(); } - if (projectContexts.First().LockFile == null) + var defaultProjectContext = projectContexts.First(); + + if (defaultProjectContext.LockFile == null) { - throw new Exception("Restore must be run prior to project migration."); + MigrationErrorCodes.MIGRATE1012( + $"project.lock.json not found in {projectDirectory}, please run dotnet restore before doing migration").Throw(); + } + + var diagnostics = defaultProjectContext.ProjectFile.Diagnostics; + if (diagnostics.Any()) + { + MigrationErrorCodes.MIGRATE1011( + $"{projectDirectory}{Environment.NewLine}{string.Join(Environment.NewLine, diagnostics.Select(d => d.Message))}") + .Throw(); + } + + var compilerName = + defaultProjectContext.ProjectFile.GetCompilerOptions(defaultProjectContext.TargetFramework, "_") + .CompilerName; + if (!compilerName.Equals("csc", StringComparison.OrdinalIgnoreCase)) + { + MigrationErrorCodes.MIGRATE20013( + $"Cannot migrate project {defaultProjectContext.ProjectFile.ProjectFilePath} using compiler {compilerName}").Throw(); } } + private void SetupOutputDirectory(string projectDirectory, string outputDirectory) + { + if (!Directory.Exists(outputDirectory)) + { + Directory.CreateDirectory(outputDirectory); + } + + if (projectDirectory != outputDirectory) + { + CopyProjectToOutputDirectory(projectDirectory, outputDirectory); + } + } + + private void CopyProjectToOutputDirectory(string projectDirectory, string outputDirectory) + { + var sourceFilePaths = Directory.EnumerateFiles(projectDirectory, "*", SearchOption.AllDirectories); + + foreach (var sourceFilePath in sourceFilePaths) + { + var relativeFilePath = PathUtility.GetRelativePath(projectDirectory, sourceFilePath); + var destinationFilePath = Path.Combine(outputDirectory, relativeFilePath); + var destinationDirectory = Path.GetDirectoryName(destinationFilePath); + + if (!Directory.Exists(destinationDirectory)) + { + Directory.CreateDirectory(destinationDirectory); + } + + File.Copy(sourceFilePath, destinationFilePath); + } + } } } diff --git a/src/Microsoft.DotNet.ProjectJsonMigration/Properties/AssemblyInfo.cs b/src/Microsoft.DotNet.ProjectJsonMigration/Properties/AssemblyInfo.cs index 9ede032e7..d766b835c 100644 --- a/src/Microsoft.DotNet.ProjectJsonMigration/Properties/AssemblyInfo.cs +++ b/src/Microsoft.DotNet.ProjectJsonMigration/Properties/AssemblyInfo.cs @@ -1,3 +1,6 @@ -using System.Runtime.CompilerServices; +// 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.Runtime.CompilerServices; [assembly: InternalsVisibleTo("Microsoft.DotNet.ProjectJsonMigration.Tests")] diff --git a/src/Microsoft.DotNet.ProjectJsonMigration/Rules/IMigrationRule.cs b/src/Microsoft.DotNet.ProjectJsonMigration/Rules/IMigrationRule.cs index a6283e180..36cc3715c 100644 --- a/src/Microsoft.DotNet.ProjectJsonMigration/Rules/IMigrationRule.cs +++ b/src/Microsoft.DotNet.ProjectJsonMigration/Rules/IMigrationRule.cs @@ -1,18 +1,7 @@ // 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 +namespace Microsoft.DotNet.ProjectJsonMigration.Rules { public interface IMigrationRule { diff --git a/src/Microsoft.DotNet.ProjectJsonMigration/Rules/MigrateBuildOptionsRule.cs b/src/Microsoft.DotNet.ProjectJsonMigration/Rules/MigrateBuildOptionsRule.cs index c66ce8005..cca260cd6 100644 --- a/src/Microsoft.DotNet.ProjectJsonMigration/Rules/MigrateBuildOptionsRule.cs +++ b/src/Microsoft.DotNet.ProjectJsonMigration/Rules/MigrateBuildOptionsRule.cs @@ -3,38 +3,31 @@ 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.Build.Construction; +using Microsoft.DotNet.ProjectJsonMigration.Transforms; +using Microsoft.DotNet.ProjectModel; using Microsoft.DotNet.ProjectModel.Files; -using Microsoft.DotNet.Cli.Utils; using Newtonsoft.Json.Linq; +using NuGet.Frameworks; -using Project = Microsoft.DotNet.ProjectModel.Project; - -namespace Microsoft.DotNet.ProjectJsonMigration +namespace Microsoft.DotNet.ProjectJsonMigration.Rules { // 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[] EmitEntryPointTransforms - => new AddPropertyTransform[] + => new [] { new AddPropertyTransform("OutputType", "Exe", compilerOptions => compilerOptions.EmitEntryPoint != null && compilerOptions.EmitEntryPoint.Value), - new AddPropertyTransform("TargetExt", ".dll", - compilerOptions => compilerOptions.EmitEntryPoint != null && compilerOptions.EmitEntryPoint.Value), new AddPropertyTransform("OutputType", "Library", compilerOptions => compilerOptions.EmitEntryPoint == null || !compilerOptions.EmitEntryPoint.Value) }; private AddPropertyTransform[] KeyFileTransforms - => new AddPropertyTransform[] + => new [] { new AddPropertyTransform("AssemblyOriginatorKeyFile", compilerOptions => compilerOptions.KeyFile, @@ -123,7 +116,7 @@ namespace Microsoft.DotNet.ProjectJsonMigration private IncludeContextTransform CopyToOutputFilesTransform => new IncludeContextTransform("Content", transformMappings: true) - .WithMetadata("CopyToOutputDirectory", "PreserveNewest"); + .WithMetadata("CopyToOutputDirectory", "PreserveNewest"); private Func> CompileFilesTransformExecute => (compilerOptions, projectDirectory) => @@ -137,14 +130,15 @@ namespace Microsoft.DotNet.ProjectJsonMigration (compilerOptions, projectDirectory) => CopyToOutputFilesTransform.Transform(GetCopyToOutputIncludeContext(compilerOptions, projectDirectory)); - private string _configuration; - private ProjectPropertyGroupElement _configurationPropertyGroup; - private ProjectItemGroupElement _configurationItemGroup; + private readonly string _configuration; + private readonly NuGetFramework _framework; + private readonly ProjectPropertyGroupElement _configurationPropertyGroup; + private readonly ProjectItemGroupElement _configurationItemGroup; private List> _propertyTransforms; private List>> _includeContextTransformExecutes; - private ITransformApplicator _transformApplicator; + private readonly ITransformApplicator _transformApplicator; public MigrateBuildOptionsRule(ITransformApplicator transformApplicator = null) { @@ -154,11 +148,13 @@ namespace Microsoft.DotNet.ProjectJsonMigration public MigrateBuildOptionsRule( string configuration, + NuGetFramework framework, ProjectPropertyGroupElement configurationPropertyGroup, ProjectItemGroupElement configurationItemGroup, ITransformApplicator transformApplicator = null) { _configuration = configuration; + _framework = framework; _configurationPropertyGroup = configurationPropertyGroup; _configurationItemGroup = configurationItemGroup; _transformApplicator = transformApplicator ?? new TransformApplicator(); @@ -207,7 +203,7 @@ namespace Microsoft.DotNet.ProjectJsonMigration var compilerOptions = projectContext.ProjectFile.GetCompilerOptions(projectContext.TargetFramework, null); var configurationCompilerOptions = - projectContext.ProjectFile.GetCompilerOptions(projectContext.TargetFramework, _configuration); + projectContext.ProjectFile.GetCompilerOptions(_framework, _configuration); // If we're in a configuration, we need to be careful not to overwrite values from BuildOptions // without a configuration @@ -293,10 +289,11 @@ namespace Microsoft.DotNet.ProjectJsonMigration 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."); + MigrationTrace.Instance.WriteLine(item1.Exclude); + MigrationTrace.Instance.WriteLine(item2Excludes.ToString()); + + MigrationErrorCodes.MIGRATE20012("Unable to migrate projects with excluded files in configurations.") + .Throw(); } } } diff --git a/src/Microsoft.DotNet.ProjectJsonMigration/Rules/MigrateConfigurationsRule.cs b/src/Microsoft.DotNet.ProjectJsonMigration/Rules/MigrateConfigurationsRule.cs index c9b85916c..7bcef379a 100644 --- a/src/Microsoft.DotNet.ProjectJsonMigration/Rules/MigrateConfigurationsRule.cs +++ b/src/Microsoft.DotNet.ProjectJsonMigration/Rules/MigrateConfigurationsRule.cs @@ -3,16 +3,11 @@ 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.Build.Construction; +using NuGet.Frameworks; -namespace Microsoft.DotNet.ProjectJsonMigration +namespace Microsoft.DotNet.ProjectJsonMigration.Rules { public class MigrateConfigurationsRule : IMigrationRule { @@ -21,19 +16,29 @@ namespace Microsoft.DotNet.ProjectJsonMigration var projectContext = migrationRuleInputs.DefaultProjectContext; var configurations = projectContext.ProjectFile.GetConfigurations().ToList(); + var frameworks = new List(); + frameworks.Add(null); + frameworks.AddRange(projectContext.ProjectFile.GetTargetFrameworks().Select(t => t.FrameworkName)); + if (!configurations.Any()) { return; } - foreach (var configuration in configurations) + var frameworkConfigurationCombinations = frameworks.SelectMany(f => configurations, Tuple.Create); + + foreach (var entry in frameworkConfigurationCombinations) { - MigrateConfiguration(configuration, migrationSettings, migrationRuleInputs); + var framework = entry.Item1; + var configuration = entry.Item2; + + MigrateConfiguration(configuration, framework, migrationSettings, migrationRuleInputs); } } private void MigrateConfiguration( - string configuration, + string configuration, + NuGetFramework framework, MigrationSettings migrationSettings, MigrationRuleInputs migrationRuleInputs) { @@ -43,10 +48,15 @@ namespace Microsoft.DotNet.ProjectJsonMigration 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, propertyGroup, itemGroup) + new MigrateBuildOptionsRule(configuration, framework, propertyGroup, itemGroup) .Apply(migrationSettings, migrationRuleInputs); propertyGroup.RemoveIfEmpty(); diff --git a/src/Microsoft.DotNet.ProjectJsonMigration/Rules/MigrateProjectDependenciesRule.cs b/src/Microsoft.DotNet.ProjectJsonMigration/Rules/MigrateProjectDependenciesRule.cs index 0b78b044e..8caa29d62 100644 --- a/src/Microsoft.DotNet.ProjectJsonMigration/Rules/MigrateProjectDependenciesRule.cs +++ b/src/Microsoft.DotNet.ProjectJsonMigration/Rules/MigrateProjectDependenciesRule.cs @@ -1,29 +1,24 @@ // 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 System.Linq; +using Microsoft.Build.Construction; +using Microsoft.DotNet.ProjectJsonMigration.Transforms; +using Microsoft.DotNet.ProjectModel; 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 +namespace Microsoft.DotNet.ProjectJsonMigration.Rules { public class MigrateProjectDependenciesRule : IMigrationRule { private readonly ITransformApplicator _transformApplicator; private string _projectDirectory; - public MigrateProjectDependenciesRule(TransformApplicator transformApplicator = null) + public MigrateProjectDependenciesRule(ITransformApplicator transformApplicator = null) { _transformApplicator = transformApplicator ?? new TransformApplicator(); } @@ -38,29 +33,34 @@ namespace Microsoft.DotNet.ProjectJsonMigration var projectDependencyTransformResults = projectExports.Select(projectExport => ProjectDependencyTransform.Transform(projectExport)); - var propertyTransformResults = new [] + + if (projectDependencyTransformResults.Any()) + { + AddPropertyTransformsToCommonPropertyGroup(migrationRuleInputs.CommonPropertyGroup); + AddProjectDependenciesToNewItemGroup(csproj.AddItemGroup(), projectDependencyTransformResults); + } + } + + private void AddProjectDependenciesToNewItemGroup(ProjectItemGroupElement itemGroup, IEnumerable projectDependencyTransformResults) + { + foreach (var projectDependencyTransformResult in projectDependencyTransformResults) + { + _transformApplicator.Execute(projectDependencyTransformResult, itemGroup); + } + } + + private void AddPropertyTransformsToCommonPropertyGroup(ProjectPropertyGroupElement commonPropertyGroup) + { + var propertyTransformResults = new[] { AutoUnifyTransform.Transform(true), DesignTimeAutoUnifyTransform.Transform(true) }; - if (projectDependencyTransformResults.Any()) + foreach (var propertyTransformResult in propertyTransformResults) { - // 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); - } + _transformApplicator.Execute(propertyTransformResult, commonPropertyGroup); } - } private AddPropertyTransform AutoUnifyTransform => new AddPropertyTransform( @@ -79,7 +79,8 @@ namespace Microsoft.DotNet.ProjectJsonMigration { if (!export.Library.Resolved) { - throw new Exception("Cannot migrate unresolved project dependency, please ensure restore has been run."); + MigrationErrorCodes.MIGRATE1014( + $"Unresolved project dependency ({export.Library.Identity.Name})").Throw(); } var projectFile = ((ProjectDescription)export.Library).Project.ProjectFilePath; diff --git a/src/Microsoft.DotNet.ProjectJsonMigration/Rules/MigratePublishOptionsRule.cs b/src/Microsoft.DotNet.ProjectJsonMigration/Rules/MigratePublishOptionsRule.cs index 52a00095e..53c6b30d0 100644 --- a/src/Microsoft.DotNet.ProjectJsonMigration/Rules/MigratePublishOptionsRule.cs +++ b/src/Microsoft.DotNet.ProjectJsonMigration/Rules/MigratePublishOptionsRule.cs @@ -1,25 +1,16 @@ // 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; +using Microsoft.DotNet.ProjectJsonMigration.Transforms; -namespace Microsoft.DotNet.ProjectJsonMigration +namespace Microsoft.DotNet.ProjectJsonMigration.Rules { public class MigratePublishOptionsRule : IMigrationRule { private readonly ITransformApplicator _transformApplicator; - public MigratePublishOptionsRule(TransformApplicator transformApplicator = null) + public MigratePublishOptionsRule(ITransformApplicator transformApplicator = null) { _transformApplicator = transformApplicator ?? new TransformApplicator(); } @@ -29,16 +20,13 @@ namespace Microsoft.DotNet.ProjectJsonMigration var csproj = migrationRuleInputs.OutputMSBuildProject; var projectContext = migrationRuleInputs.DefaultProjectContext; - var transformResults = new[] - { - CopyToOutputFilesTransform.Transform(projectContext.ProjectFile.PublishOptions) - }; + var transformResult = CopyToOutputFilesTransform.Transform(projectContext.ProjectFile.PublishOptions); - if (transformResults.Any(t => t != null && t.Any())) + if (transformResult != null && transformResult.Any()) { var itemGroup = migrationRuleInputs.CommonItemGroup; _transformApplicator.Execute( - CopyToOutputFilesTransform.Transform(projectContext.ProjectFile.PublishOptions), + transformResult, itemGroup, mergeExisting: true); } @@ -46,7 +34,6 @@ namespace Microsoft.DotNet.ProjectJsonMigration private IncludeContextTransform CopyToOutputFilesTransform => new IncludeContextTransform("Content", transformMappings: true) - .WithMetadata("CopyToOutputDirectory", "None") - .WithMetadata("CopyToPublishDirectory", "PreserveNewest"); + .WithMetadata("CopyToPublishDirectory", "PreserveNewest"); } } diff --git a/src/Microsoft.DotNet.ProjectJsonMigration/Rules/MigrateRootOptionsRule.cs b/src/Microsoft.DotNet.ProjectJsonMigration/Rules/MigrateRootOptionsRule.cs index 357c1901e..23c329d68 100644 --- a/src/Microsoft.DotNet.ProjectJsonMigration/Rules/MigrateRootOptionsRule.cs +++ b/src/Microsoft.DotNet.ProjectJsonMigration/Rules/MigrateRootOptionsRule.cs @@ -1,27 +1,18 @@ // 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 Microsoft.DotNet.ProjectJsonMigration.Transforms; using Project = Microsoft.DotNet.ProjectModel.Project; -namespace Microsoft.DotNet.ProjectJsonMigration +namespace Microsoft.DotNet.ProjectJsonMigration.Rules { public class MigrateRootOptionsRule : IMigrationRule { private readonly ITransformApplicator _transformApplicator; private readonly AddPropertyTransform[] _transforms; - public MigrateRootOptionsRule(TransformApplicator transformApplicator = null) + public MigrateRootOptionsRule(ITransformApplicator transformApplicator = null) { _transformApplicator = transformApplicator ?? new TransformApplicator(); diff --git a/src/Microsoft.DotNet.ProjectJsonMigration/Rules/MigrateRuntimeOptionsRule.cs b/src/Microsoft.DotNet.ProjectJsonMigration/Rules/MigrateRuntimeOptionsRule.cs index 3b59c7a63..e865fe39a 100644 --- a/src/Microsoft.DotNet.ProjectJsonMigration/Rules/MigrateRuntimeOptionsRule.cs +++ b/src/Microsoft.DotNet.ProjectJsonMigration/Rules/MigrateRuntimeOptionsRule.cs @@ -1,18 +1,9 @@ // 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 +namespace Microsoft.DotNet.ProjectJsonMigration.Rules { public class MigrateRuntimeOptionsRule : IMigrationRule { @@ -28,7 +19,8 @@ namespace Microsoft.DotNet.ProjectJsonMigration { if (File.Exists(outputRuntimeOptionsFile)) { - throw new Exception("Runtime options file already exists. Has migration already been run?"); + MigrationErrorCodes.MIGRATE1015( + $"{outputRuntimeOptionsFile} already exists. Has migration already been run?").Throw(); } File.WriteAllText(outputRuntimeOptionsFile, raw); diff --git a/src/Microsoft.DotNet.ProjectJsonMigration/Rules/MigrateScriptsRule.cs b/src/Microsoft.DotNet.ProjectJsonMigration/Rules/MigrateScriptsRule.cs index 9eb18cf72..8eb776b91 100644 --- a/src/Microsoft.DotNet.ProjectJsonMigration/Rules/MigrateScriptsRule.cs +++ b/src/Microsoft.DotNet.ProjectJsonMigration/Rules/MigrateScriptsRule.cs @@ -1,20 +1,13 @@ // Copyright (c) .NET Foundation and contributors. All rights reserved. // Licensed under the MIT license. See LICENSE file in the project root for full license information. -using System; 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.Build.Construction; using Microsoft.DotNet.Cli.Utils.CommandParsing; -using Newtonsoft.Json; -using Microsoft.DotNet.ProjectModel.Files; -using Microsoft.DotNet.Tools.Common; +using Microsoft.DotNet.ProjectJsonMigration.Transforms; -namespace Microsoft.DotNet.ProjectJsonMigration +namespace Microsoft.DotNet.ProjectJsonMigration.Rules { public class MigrateScriptsRule : IMigrationRule { @@ -23,7 +16,7 @@ namespace Microsoft.DotNet.ProjectJsonMigration private readonly ITransformApplicator _transformApplicator; - public MigrateScriptsRule(TransformApplicator transformApplicator = null) + public MigrateScriptsRule(ITransformApplicator transformApplicator = null) { _transformApplicator = transformApplicator ?? new TransformApplicator(); } @@ -49,7 +42,7 @@ namespace Microsoft.DotNet.ProjectJsonMigration var count = 0; foreach (var scriptCommand in scriptCommands) { - var scriptExtensionPropertyName = AddScriptExtension(propertyGroup, scriptCommand, (++count).ToString()); + var scriptExtensionPropertyName = AddScriptExtension(propertyGroup, scriptCommand, $"{scriptSetName}_{++count}"); AddExec(target, FormatScriptCommand(scriptCommand, scriptExtensionPropertyName)); } @@ -131,8 +124,9 @@ namespace Microsoft.DotNet.ProjectJsonMigration { if (msbuildMapping == null) { - throw new Exception( - $"{scriptVariableName} is currently an unsupported script variable for project migration"); + MigrationErrorCodes.MIGRATE1016( + $"{scriptVariableName} is currently an unsupported script variable for project migration") + .Throw(); } command = command.Replace($"%{scriptVariableName}%", msbuildMapping); @@ -152,7 +146,8 @@ namespace Microsoft.DotNet.ProjectJsonMigration var targetName = $"{scriptSetName[0].ToString().ToUpper()}{string.Concat(scriptSetName.Skip(1))}Script"; var targetHookInfo = ScriptSetToMSBuildHookTargetMap[scriptSetName]; - var target = csproj.AddTarget(targetName); + var target = csproj.CreateTargetElement(targetName); + csproj.InsertBeforeChild(target, csproj.LastChild); if (targetHookInfo.IsRunBefore) { target.BeforeTargets = targetHookInfo.TargetName; @@ -186,12 +181,12 @@ namespace Microsoft.DotNet.ProjectJsonMigration { "compile:ResponseFile", null }, // Not migrated { "compile:CompilerExitCode", null }, // Not migrated { "compile:RuntimeOutputDir", null }, // Not migrated - { "compile:RuntimeIdentifier", null },// TODO: Need Rid in CSProj + { "compile:RuntimeIdentifier", null },// Not Migrated { "publish:TargetFramework", null }, // TODO: Need Short framework name in CSProj - { "publish:Runtime", null }, // TODO: Need Rid in CSProj + { "publish:Runtime", "$(RuntimeIdentifier)" }, - { "compile:FullTargetFramework", "$(TargetFrameworkIdentifier)=$(TargetFrameworkVersion)" }, + { "compile:FullTargetFramework", "$(TargetFrameworkIdentifier),Version=$(TargetFrameworkVersion)" }, { "compile:Configuration", "$(Configuration)" }, { "compile:OutputFile", "$(TargetPath)" }, { "compile:OutputDir", "$(TargetDir)" }, @@ -199,10 +194,10 @@ namespace Microsoft.DotNet.ProjectJsonMigration { "publish:ProjectPath", "$(MSBuildThisFileDirectory)" }, { "publish:Configuration", "$(Configuration)" }, { "publish:OutputPath", "$(TargetDir)" }, - { "publish:FullTargetFramework", "$(TargetFrameworkIdentifier)=$(TargetFrameworkVersion)" }, + { "publish:FullTargetFramework", "$(TargetFrameworkIdentifier),Version=$(TargetFrameworkVersion)" }, { "project:Directory", "$(MSBuildProjectDirectory)" }, - { "project:Name", "$(MSBuildThisFileName)" }, + { "project:Name", "$(AssemblyName)" }, { "project:Version", "$(Version)" } }; @@ -211,14 +206,6 @@ namespace Microsoft.DotNet.ProjectJsonMigration public bool IsRunBefore { get; } public string TargetName { get; } - public string BeforeAfterTarget - { - get - { - return IsRunBefore ? "BeforeTargets" : "AfterTargets"; - } - } - public TargetHookInfo(bool isRunBefore, string targetName) { IsRunBefore = isRunBefore; diff --git a/src/Microsoft.DotNet.ProjectJsonMigration/Rules/MigrateTFMRule.cs b/src/Microsoft.DotNet.ProjectJsonMigration/Rules/MigrateTFMRule.cs index 343c33667..b1ed6af73 100644 --- a/src/Microsoft.DotNet.ProjectJsonMigration/Rules/MigrateTFMRule.cs +++ b/src/Microsoft.DotNet.ProjectJsonMigration/Rules/MigrateTFMRule.cs @@ -1,11 +1,15 @@ -using System; -using System.Text; +// 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.Globalization; -using Microsoft.Build.Construction; using System.Linq; +using System.Text; +using Microsoft.Build.Construction; +using Microsoft.DotNet.ProjectJsonMigration.Transforms; using NuGet.Frameworks; -namespace Microsoft.DotNet.ProjectJsonMigration +namespace Microsoft.DotNet.ProjectJsonMigration.Rules { // TODO: Support Multi-TFM public class MigrateTFMRule : IMigrationRule @@ -13,7 +17,7 @@ namespace Microsoft.DotNet.ProjectJsonMigration private readonly ITransformApplicator _transformApplicator; private readonly AddPropertyTransform[] _transforms; - public MigrateTFMRule(TransformApplicator transformApplicator = null) + public MigrateTFMRule(ITransformApplicator transformApplicator = null) { _transformApplicator = transformApplicator ?? new TransformApplicator(); @@ -43,14 +47,11 @@ namespace Microsoft.DotNet.ProjectJsonMigration 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); + var properties = csproj.Properties.Where(p => existingPropertiesToRemove.Contains(p.Name)); - foreach (var property in properties) - { - property.Parent.RemoveChild(property); - } + foreach (var property in properties) + { + property.Parent.RemoveChild(property); } } diff --git a/src/Microsoft.DotNet.ProjectJsonMigration/Rules/MigrateXprojProjectReferencesRule.cs b/src/Microsoft.DotNet.ProjectJsonMigration/Rules/MigrateXprojProjectReferencesRule.cs index 1b5eee495..1d51ea51a 100644 --- a/src/Microsoft.DotNet.ProjectJsonMigration/Rules/MigrateXprojProjectReferencesRule.cs +++ b/src/Microsoft.DotNet.ProjectJsonMigration/Rules/MigrateXprojProjectReferencesRule.cs @@ -1,16 +1,12 @@ using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; -namespace Microsoft.DotNet.ProjectJsonMigration +namespace Microsoft.DotNet.ProjectJsonMigration.Rules { - // TODO: XProj ProjectToProject references public class MigrateXprojProjectReferencesRule : IMigrationRule { public void Apply(MigrationSettings migrationSettings, MigrationRuleInputs migrationRuleInputs) { - throw new NotImplementedException(); + throw new NotImplementedException("TODO: XProj ProjectToProject references"); } } } diff --git a/src/Microsoft.DotNet.ProjectJsonMigration/Rules/SaveOutputProjectRule.cs b/src/Microsoft.DotNet.ProjectJsonMigration/Rules/SaveOutputProjectRule.cs index adfba4fbb..78c06692a 100644 --- a/src/Microsoft.DotNet.ProjectJsonMigration/Rules/SaveOutputProjectRule.cs +++ b/src/Microsoft.DotNet.ProjectJsonMigration/Rules/SaveOutputProjectRule.cs @@ -1,18 +1,9 @@ // 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 +namespace Microsoft.DotNet.ProjectJsonMigration.Rules { public class SaveOutputProjectRule : IMigrationRule { diff --git a/src/Microsoft.DotNet.ProjectJsonMigration/Rules/TemporaryMutateProjectJsonRule.cs b/src/Microsoft.DotNet.ProjectJsonMigration/Rules/TemporaryMutateProjectJsonRule.cs index c15f751b6..3de53b547 100644 --- a/src/Microsoft.DotNet.ProjectJsonMigration/Rules/TemporaryMutateProjectJsonRule.cs +++ b/src/Microsoft.DotNet.ProjectJsonMigration/Rules/TemporaryMutateProjectJsonRule.cs @@ -1,14 +1,12 @@ -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; +// 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. -namespace Microsoft.DotNet.ProjectJsonMigration +using System; +using System.IO; +using System.Linq; +using Newtonsoft.Json.Linq; + +namespace Microsoft.DotNet.ProjectJsonMigration.Rules { /// /// This rule is temporary while project.json still exists in the new project system. @@ -21,9 +19,6 @@ namespace Microsoft.DotNet.ProjectJsonMigration /// 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); @@ -44,7 +39,7 @@ namespace Microsoft.DotNet.ProjectJsonMigration } var json = CreateDestinationProjectFile(sourceProjectFile, destinationProjectFile); - InjectSdkReference(json, s_sdkPackageName, migrationSettings.SdkPackageVersion); + InjectSdkReference(json, ConstantPackageNames.CSdkPackageName, migrationSettings.SdkPackageVersion); RemoveRuntimesNode(json); File.WriteAllText(destinationProjectFile, json.ToString()); diff --git a/src/Microsoft.DotNet.ProjectJsonMigration/project.json b/src/Microsoft.DotNet.ProjectJsonMigration/project.json index 3dcdf68d5..01713004a 100644 --- a/src/Microsoft.DotNet.ProjectJsonMigration/project.json +++ b/src/Microsoft.DotNet.ProjectJsonMigration/project.json @@ -13,20 +13,12 @@ "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"] } } } diff --git a/src/Microsoft.DotNet.ProjectJsonMigration/transforms/AddBoolPropertyTransform.cs b/src/Microsoft.DotNet.ProjectJsonMigration/transforms/AddBoolPropertyTransform.cs deleted file mode 100644 index d137d7f36..000000000 --- a/src/Microsoft.DotNet.ProjectJsonMigration/transforms/AddBoolPropertyTransform.cs +++ /dev/null @@ -1,25 +0,0 @@ -// 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 - { - public AddBoolPropertyTransform(string propertyName) - : base(propertyName, b => b.ToString(), b => b) { } - - public AddBoolPropertyTransform(string propertyName, Func condition) - : base(propertyName, b => b.ToString(), condition) { } - } -} diff --git a/src/Microsoft.DotNet.ProjectJsonMigration/transforms/AddItemTransform.cs b/src/Microsoft.DotNet.ProjectJsonMigration/transforms/AddItemTransform.cs index fde54f3ed..0d09e6bce 100644 --- a/src/Microsoft.DotNet.ProjectJsonMigration/transforms/AddItemTransform.cs +++ b/src/Microsoft.DotNet.ProjectJsonMigration/transforms/AddItemTransform.cs @@ -10,32 +10,30 @@ using Microsoft.DotNet.Cli.Utils; using Microsoft.DotNet.Cli; using System.Linq; using System.IO; +using Microsoft.DotNet.ProjectJsonMigration.Models; using Newtonsoft.Json; -namespace Microsoft.DotNet.ProjectJsonMigration +namespace Microsoft.DotNet.ProjectJsonMigration.Transforms { public class AddItemTransform : ConditionalTransform { - private ProjectRootElement _itemObjectGenerator = ProjectRootElement.Create(); + private readonly ProjectRootElement _itemObjectGenerator = ProjectRootElement.Create(); - private string _itemName; - private string _includeValue; - private string _excludeValue; + private readonly string _itemName; + private readonly string _includeValue; + private readonly string _excludeValue; - private Func _includeValueFunc; - private Func _excludeValueFunc; - - private bool _mergeExisting; + private readonly Func _includeValueFunc; + private readonly Func _excludeValueFunc; - private List> _metadata = new List>(); + private readonly List> _metadata = new List>(); public AddItemTransform( string itemName, IEnumerable includeValues, IEnumerable excludeValues, - Func condition, - bool mergeExisting = false) - : this(itemName, string.Join(";", includeValues), string.Join(";", excludeValues), condition, mergeExisting) { } + Func condition) + : this(itemName, string.Join(";", includeValues), string.Join(";", excludeValues), condition) { } public AddItemTransform( string itemName, @@ -77,14 +75,12 @@ namespace Microsoft.DotNet.ProjectJsonMigration string itemName, string includeValue, string excludeValue, - Func condition, - bool mergeExisting=false) + Func condition) : base(condition) { _itemName = itemName; _includeValue = includeValue; _excludeValue = excludeValue; - _mergeExisting = mergeExisting; } public AddItemTransform WithMetadata(string metadataName, string metadataValue) diff --git a/src/Microsoft.DotNet.ProjectJsonMigration/transforms/AddPropertyTransform.cs b/src/Microsoft.DotNet.ProjectJsonMigration/transforms/AddPropertyTransform.cs index 48231ee40..f2cb30fea 100644 --- a/src/Microsoft.DotNet.ProjectJsonMigration/transforms/AddPropertyTransform.cs +++ b/src/Microsoft.DotNet.ProjectJsonMigration/transforms/AddPropertyTransform.cs @@ -12,7 +12,7 @@ using System.Linq; using System.IO; using Newtonsoft.Json; -namespace Microsoft.DotNet.ProjectJsonMigration +namespace Microsoft.DotNet.ProjectJsonMigration.Transforms { public class AddPropertyTransform : ConditionalTransform { diff --git a/src/Microsoft.DotNet.ProjectJsonMigration/transforms/AddStringPropertyTransform.cs b/src/Microsoft.DotNet.ProjectJsonMigration/transforms/AddStringPropertyTransform.cs deleted file mode 100644 index facca2e4d..000000000 --- a/src/Microsoft.DotNet.ProjectJsonMigration/transforms/AddStringPropertyTransform.cs +++ /dev/null @@ -1,25 +0,0 @@ -// 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 - { - public AddStringPropertyTransform(string propertyName) - : base(propertyName, s => s, s => !string.IsNullOrEmpty(s)) { } - - public AddStringPropertyTransform(string propertyName, Func condition) - : base(propertyName, s => s, s => !string.IsNullOrEmpty(s) && condition(s)) { } - } -} diff --git a/src/Microsoft.DotNet.ProjectJsonMigration/transforms/ConditionalTransform.cs b/src/Microsoft.DotNet.ProjectJsonMigration/transforms/ConditionalTransform.cs index 0ccd10225..ca60d3f65 100644 --- a/src/Microsoft.DotNet.ProjectJsonMigration/transforms/ConditionalTransform.cs +++ b/src/Microsoft.DotNet.ProjectJsonMigration/transforms/ConditionalTransform.cs @@ -12,7 +12,7 @@ using System.Linq; using System.IO; using Newtonsoft.Json; -namespace Microsoft.DotNet.ProjectJsonMigration +namespace Microsoft.DotNet.ProjectJsonMigration.Transforms { public abstract class ConditionalTransform : ITransform { diff --git a/src/Microsoft.DotNet.ProjectJsonMigration/transforms/ITransform.cs b/src/Microsoft.DotNet.ProjectJsonMigration/transforms/ITransform.cs index ab9b37804..bdeaf18d0 100644 --- a/src/Microsoft.DotNet.ProjectJsonMigration/transforms/ITransform.cs +++ b/src/Microsoft.DotNet.ProjectJsonMigration/transforms/ITransform.cs @@ -1,6 +1,6 @@ using Microsoft.Build.Construction; -namespace Microsoft.DotNet.ProjectJsonMigration +namespace Microsoft.DotNet.ProjectJsonMigration.Transforms { public interface ITransform { diff --git a/src/Microsoft.DotNet.ProjectJsonMigration/transforms/ITransformApplicator.cs b/src/Microsoft.DotNet.ProjectJsonMigration/transforms/ITransformApplicator.cs index b2c7bf307..6435551ed 100644 --- a/src/Microsoft.DotNet.ProjectJsonMigration/transforms/ITransformApplicator.cs +++ b/src/Microsoft.DotNet.ProjectJsonMigration/transforms/ITransformApplicator.cs @@ -1,7 +1,7 @@ using System.Collections.Generic; using Microsoft.Build.Construction; -namespace Microsoft.DotNet.ProjectJsonMigration +namespace Microsoft.DotNet.ProjectJsonMigration.Transforms { public interface ITransformApplicator { diff --git a/src/Microsoft.DotNet.ProjectJsonMigration/transforms/IncludeContextTransform.cs b/src/Microsoft.DotNet.ProjectJsonMigration/transforms/IncludeContextTransform.cs index c935d34e5..0fd5e43fc 100644 --- a/src/Microsoft.DotNet.ProjectJsonMigration/transforms/IncludeContextTransform.cs +++ b/src/Microsoft.DotNet.ProjectJsonMigration/transforms/IncludeContextTransform.cs @@ -6,8 +6,9 @@ using System.Linq; using System.Threading.Tasks; using Microsoft.Build.Construction; using Microsoft.DotNet.Cli.Utils; +using Microsoft.DotNet.ProjectJsonMigration.Models; -namespace Microsoft.DotNet.ProjectJsonMigration +namespace Microsoft.DotNet.ProjectJsonMigration.Transforms { public class IncludeContextTransform : ConditionalTransform> { @@ -15,9 +16,9 @@ namespace Microsoft.DotNet.ProjectJsonMigration // - Partially solved, what if the resolved glob is a directory? // TODO: Support mappings - private string _itemName; + private readonly string _itemName; private bool _transformMappings; - private List> _metadata = new List>(); + private readonly List> _metadata = new List>(); private AddItemTransform[] _transformSet; public IncludeContextTransform( @@ -27,8 +28,6 @@ namespace Microsoft.DotNet.ProjectJsonMigration { _itemName = itemName; _transformMappings = transformMappings; - - CreateTransformSet(); } public IncludeContextTransform WithMetadata(string metadataName, string metadataValue) @@ -84,7 +83,7 @@ namespace Microsoft.DotNet.ProjectJsonMigration includeExcludeTransformation.WithMetadata(metadata); } - _transformSet = new AddItemTransform[] + _transformSet = new [] { includeFilesExcludeFilesTransformation, includeExcludeTransformation @@ -138,6 +137,8 @@ namespace Microsoft.DotNet.ProjectJsonMigration public override IEnumerable ConditionallyTransform(IncludeContext source) { + CreateTransformSet(); + return _transformSet.Select(t => t.Transform(source)); } } diff --git a/src/Microsoft.DotNet.ProjectJsonMigration/transforms/TransformApplicator.cs b/src/Microsoft.DotNet.ProjectJsonMigration/transforms/TransformApplicator.cs index 6a13154a3..97280eaa2 100644 --- a/src/Microsoft.DotNet.ProjectJsonMigration/transforms/TransformApplicator.cs +++ b/src/Microsoft.DotNet.ProjectJsonMigration/transforms/TransformApplicator.cs @@ -12,7 +12,7 @@ using System.Linq; using System.IO; using Newtonsoft.Json; -namespace Microsoft.DotNet.ProjectJsonMigration +namespace Microsoft.DotNet.ProjectJsonMigration.Transforms { public class TransformApplicator : ITransformApplicator { @@ -126,19 +126,19 @@ namespace Microsoft.DotNet.ProjectJsonMigration throw new InvalidOperationException("Cannot merge items of different types."); } - if (!item.CommonIncludes(existingItem).Any()) + if (!item.IntersectIncludes(existingItem).Any()) { throw new InvalidOperationException("Cannot merge items without a common include."); } - var commonIncludes = item.CommonIncludes(existingItem).ToList(); + var commonIncludes = item.IntersectIncludes(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.UnionExcludes(existingItem.Excludes()); + mergedItem.UnionExcludes(item.Excludes()); mergedItem.AddMetadata(existingItem.Metadata); mergedItem.AddMetadata(item.Metadata); @@ -157,7 +157,7 @@ namespace Microsoft.DotNet.ProjectJsonMigration { return project.ItemsWithoutConditions() .Where(i => string.Equals(i.ItemType, item.ItemType, StringComparison.Ordinal)) - .Where(i => i.CommonIncludes(item).Any()); + .Where(i => i.IntersectIncludes(item).Any()); } private class MergeResult diff --git a/src/dotnet/Properties/AssemblyInfo.cs b/src/dotnet/Properties/AssemblyInfo.cs index c6553d456..0dba7c570 100644 --- a/src/dotnet/Properties/AssemblyInfo.cs +++ b/src/dotnet/Properties/AssemblyInfo.cs @@ -2,5 +2,4 @@ using System.Reflection; using System.Runtime.CompilerServices; [assembly: AssemblyMetadataAttribute("Serviceable", "True")] -[assembly: InternalsVisibleTo("dotnet.Tests")] -[assembly: InternalsVisibleTo("Microsoft.DotNet.ProjectJsonMigration.Tests")] \ No newline at end of file +[assembly: InternalsVisibleTo("dotnet.Tests")] \ No newline at end of file diff --git a/src/dotnet/commands/dotnet-migrate/MigrateCommand.cs b/src/dotnet/commands/dotnet-migrate/MigrateCommand.cs index 430e486c1..3b67f26bb 100644 --- a/src/dotnet/commands/dotnet-migrate/MigrateCommand.cs +++ b/src/dotnet/commands/dotnet-migrate/MigrateCommand.cs @@ -6,17 +6,18 @@ using System.IO; using Microsoft.Build.Construction; using Microsoft.DotNet.Cli; using Microsoft.DotNet.ProjectJsonMigration; +using Microsoft.DotNet.ProjectModel; namespace Microsoft.DotNet.Tools.Migrate { public partial class MigrateCommand { - private string _templateFile; - private string _outputDirectory; - private string _projectJson; - private string _sdkVersion; + private readonly string _templateFile; + private readonly string _outputDirectory; + private readonly string _projectJson; + private readonly string _sdkVersion; - private TemporaryDotnetNewTemplateProject _temporaryDotnetNewProject; + private readonly TemporaryDotnetNewTemplateProject _temporaryDotnetNewProject; public MigrateCommand(string templateFile, string outputDirectory, string projectJson, string sdkVersion) { @@ -28,23 +29,24 @@ namespace Microsoft.DotNet.Tools.Migrate _temporaryDotnetNewProject = new TemporaryDotnetNewTemplateProject(); } - public int Start() + public int Execute() { - var project = GetProjectJsonPath(_projectJson) ?? _temporaryDotnetNewProject.ProjectJsonPath; + var project = GetProjectJsonPath(_projectJson) ?? GetProjectJsonPath(Directory.GetCurrentDirectory()); EnsureNotNull(project, "Unable to find project.json"); var projectDirectory = Path.GetDirectoryName(project); - var templateFile = _templateFile ?? _temporaryDotnetNewProject.MSBuildProjectPath; - EnsureNotNull(templateFile, "Unable to find default msbuild template"); + var msBuildTemplate = _templateFile != null ? + ProjectRootElement.TryOpen(_templateFile) : _temporaryDotnetNewProject.MSBuildProject; - var outputDirectory = _outputDirectory ?? Path.GetDirectoryName(project); + var outputDirectory = _outputDirectory ?? projectDirectory; EnsureNotNull(outputDirectory, "Null output directory"); var sdkVersion = _sdkVersion ?? new ProjectJsonParser(_temporaryDotnetNewProject.ProjectJson).SdkPackageVersion; EnsureNotNull(sdkVersion, "Null Sdk Version"); - var migrationSettings = new MigrationSettings(projectDirectory, outputDirectory, sdkVersion, templateFile); + var migrationSettings = new MigrationSettings(projectDirectory, outputDirectory, sdkVersion, msBuildTemplate); new ProjectMigrator().Migrate(migrationSettings); + return 0; } @@ -63,21 +65,13 @@ namespace Microsoft.DotNet.Tools.Migrate return null; } + projectJson = ProjectPathHelper.NormalizeProjectFilePath(projectJson); + if (File.Exists(projectJson)) { return projectJson; } - if (Directory.Exists(projectJson)) - { - var projectCandidate = Path.Combine(projectJson, "project.json"); - - if (File.Exists(projectCandidate)) - { - return projectCandidate; - } - } - throw new Exception($"Unable to find project file at {projectJson}"); } } diff --git a/src/dotnet/commands/dotnet-migrate/Program.cs b/src/dotnet/commands/dotnet-migrate/Program.cs index 8c2c5708e..4626ed2d5 100644 --- a/src/dotnet/commands/dotnet-migrate/Program.cs +++ b/src/dotnet/commands/dotnet-migrate/Program.cs @@ -13,12 +13,11 @@ namespace Microsoft.DotNet.Tools.Migrate { DebugHelper.HandleDebugSwitch(ref args); - CommandLineApplication app = new CommandLineApplication(throwOnUnexpectedArg: false); + CommandLineApplication app = new CommandLineApplication(); app.Name = "dotnet migrate"; app.FullName = ".NET Migrate Command"; app.Description = "Command used to migrate project.json projects to msbuild"; app.HandleResponseFiles = true; - app.AllowArgumentSeparator = true; app.HelpOption("-h|--help"); CommandOption template = app.Option("-t|--template-file", "Base MSBuild template to use for migrated app. The default is the project included in dotnet new -t msbuild", CommandOptionType.SingleValue); @@ -34,7 +33,7 @@ namespace Microsoft.DotNet.Tools.Migrate project.Value(), sdkVersion.Value()); - return migrateCommand.Start(); + return migrateCommand.Execute(); }); try diff --git a/src/dotnet/commands/dotnet-migrate/ProjectJsonParser.cs b/src/dotnet/commands/dotnet-migrate/ProjectJsonParser.cs index 4856a64aa..71f599ba2 100644 --- a/src/dotnet/commands/dotnet-migrate/ProjectJsonParser.cs +++ b/src/dotnet/commands/dotnet-migrate/ProjectJsonParser.cs @@ -4,8 +4,9 @@ using System.Collections.Generic; using System.IO; using System.Linq; using System.Threading.Tasks; +using Microsoft.DotNet.ProjectJsonMigration; -namespace Microsoft.DotNet.Cli +namespace Microsoft.DotNet.Tools.Migrate { /// /// Parses select data from a project.json without relying on ProjectModel. @@ -13,18 +14,16 @@ namespace Microsoft.DotNet.Cli /// internal class ProjectJsonParser { - public static string SdkPackageName => "Microsoft.DotNet.Core.Sdk"; - public string SdkPackageVersion { get; } public ProjectJsonParser(JObject projectJson) { - SdkPackageVersion = GetSdkPackageVersion(projectJson); + SdkPackageVersion = GetPackageVersion(projectJson, ConstantPackageNames.CSdkPackageName); } - private string GetSdkPackageVersion(JObject projectJson) + private string GetPackageVersion(JObject projectJson, string packageName) { - var sdkPackageNode = SelectJsonNodes(projectJson, property => property.Name == SdkPackageName).First(); + var sdkPackageNode = SelectJsonNodes(projectJson, property => property.Name == packageName).First(); if (sdkPackageNode.Value.Type == JTokenType.String) { @@ -34,10 +33,10 @@ namespace Microsoft.DotNet.Cli { var sdkPackageNodeValue = (JObject)sdkPackageNode.Value; - JToken sdkVersionNode; - if (sdkPackageNodeValue.TryGetValue("version", out sdkVersionNode)) + JToken versionNode; + if (sdkPackageNodeValue.TryGetValue("version", out versionNode)) { - return sdkVersionNode.Value(); + return versionNode.Value(); } else { diff --git a/src/dotnet/commands/dotnet-migrate/TemporaryDotnetNewTemplateProject.cs b/src/dotnet/commands/dotnet-migrate/TemporaryDotnetNewTemplateProject.cs index 86a944c4b..33cdeb796 100644 --- a/src/dotnet/commands/dotnet-migrate/TemporaryDotnetNewTemplateProject.cs +++ b/src/dotnet/commands/dotnet-migrate/TemporaryDotnetNewTemplateProject.cs @@ -6,40 +6,40 @@ using System.Collections.Generic; using System.IO; using System.Linq; using System.Threading.Tasks; +using Microsoft.DotNet.ProjectJsonMigration; -namespace Microsoft.DotNet.Cli +namespace Microsoft.DotNet.Tools.Migrate { internal class TemporaryDotnetNewTemplateProject { - private static string s_temporaryDotnetNewMSBuildProjectName = "p"; + private const string c_temporaryDotnetNewMSBuildProjectName = "p"; - public TemporaryDotnetNewTemplateProject() - { - ProjectDirectory = CreateDotnetNewMSBuild(s_temporaryDotnetNewMSBuildProjectName); - MSBuildProject = GetMSBuildProject(ProjectDirectory); - ProjectJson = GetProjectJson(ProjectDirectory); - } + private readonly string _projectDirectory; public ProjectRootElement MSBuildProject { get; } public JObject ProjectJson { get; } - public string ProjectDirectory { get; } - public string ProjectJsonPath => Path.Combine(ProjectDirectory, "project.json"); - public string MSBuildProjectPath => Path.Combine(ProjectDirectory, s_temporaryDotnetNewMSBuildProjectName); + public TemporaryDotnetNewTemplateProject() + { + _projectDirectory = CreateDotnetNewMSBuild(c_temporaryDotnetNewMSBuildProjectName); + MSBuildProject = GetMSBuildProject(_projectDirectory); + ProjectJson = GetProjectJson(_projectDirectory); + + Clean(); + } public void Clean() { - Directory.Delete(ProjectDirectory, true); + Directory.Delete(Path.Combine(_projectDirectory, ".."), true); } private string CreateDotnetNewMSBuild(string projectName) { - var guid = Guid.NewGuid().ToString(); var tempDir = Path.Combine( Path.GetTempPath(), this.GetType().Namespace, - guid, - s_temporaryDotnetNewMSBuildProjectName); + Path.GetRandomFileName(), + c_temporaryDotnetNewMSBuildProjectName); if (Directory.Exists(tempDir)) { @@ -55,7 +55,7 @@ namespace Microsoft.DotNet.Cli private ProjectRootElement GetMSBuildProject(string temporaryDotnetNewMSBuildDirectory) { var templateProjPath = Path.Combine(temporaryDotnetNewMSBuildDirectory, - s_temporaryDotnetNewMSBuildProjectName + ".csproj"); + c_temporaryDotnetNewMSBuildProjectName + ".csproj"); return ProjectRootElement.Open(templateProjPath); } @@ -78,6 +78,9 @@ namespace Microsoft.DotNet.Cli if (commandResult.ExitCode != 0) { + MigrationTrace.Instance.WriteLine(commandResult.StdOut); + MigrationTrace.Instance.WriteLine(commandResult.StdErr); + throw new Exception($"Failed to run {commandToExecute} in directory: {workingDirectory}"); } } diff --git a/test/Microsoft.DotNet.ProjectJsonMigration.Tests/GivenAProjectMigrator.cs b/test/Microsoft.DotNet.ProjectJsonMigration.Tests/GivenAProjectMigrator.cs new file mode 100644 index 000000000..8b42c70bd --- /dev/null +++ b/test/Microsoft.DotNet.ProjectJsonMigration.Tests/GivenAProjectMigrator.cs @@ -0,0 +1,90 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using FluentAssertions; +using Microsoft.Build.Construction; +using Microsoft.DotNet.ProjectJsonMigration.Rules; +using Microsoft.DotNet.ProjectModel; +using Microsoft.DotNet.Tools.Common; +using Microsoft.DotNet.Tools.Test.Utilities; +using NuGet.Frameworks; +using Xunit; + +namespace Microsoft.DotNet.ProjectJsonMigration.Tests +{ + public class GivenAProjectMigrator : TestBase + { + [Fact] + public void It_copies_ProjectDirectory_contents_to_OutputDirectory_when_the_directories_are_different() + { + var testProjectDirectory = TestAssetsManager.CreateTestInstance("TestAppSimple", callingMethod: "z") + .WithLockFiles().Path; + var outputDirectory = Temp.CreateDirectory().Path; + + var projectDirectoryRelativeFilePaths = EnumerateFilesWithRelativePath(testProjectDirectory); + + var mockProj = ProjectRootElement.Create(); + var testSettings = new MigrationSettings(testProjectDirectory, outputDirectory, "1.0.0", mockProj); + + var projectMigrator = new ProjectMigrator(new FakeEmptyMigrationRule()); + projectMigrator.Migrate(testSettings); + + foreach (var projectDirectoryRelativeFilePath in projectDirectoryRelativeFilePaths) + { + File.Exists(Path.Combine(outputDirectory, projectDirectoryRelativeFilePath)).Should().BeTrue(); + } + } + + [Fact] + public void It_throws_when_migrating_a_deprecated_projectJson() + { + var testProjectDirectory = + TestAssetsManager.CreateTestInstance("TestLibraryWithDeprecatedProjectFile", callingMethod: "z") + .WithLockFiles().Path; + + var mockProj = ProjectRootElement.Create(); + var testSettings = new MigrationSettings(testProjectDirectory, testProjectDirectory, "1.0.0", mockProj); + + var projectMigrator = new ProjectMigrator(new FakeEmptyMigrationRule()); + Action migrateAction = () => projectMigrator.Migrate(testSettings); + + migrateAction.ShouldThrow().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.")); + } + + [Fact] + public void It_throws_when_migrating_a_non_csharp_app() + { + var testProjectDirectory = + TestAssetsManager.CreateTestInstance("FSharpTestProjects/TestApp", callingMethod: "z") + .WithLockFiles().Path; + + var mockProj = ProjectRootElement.Create(); + var testSettings = new MigrationSettings(testProjectDirectory, testProjectDirectory, "1.0.0", mockProj); + + var projectMigrator = new ProjectMigrator(new FakeEmptyMigrationRule()); + Action migrateAction = () => projectMigrator.Migrate(testSettings); + + migrateAction.ShouldThrow().Where( + e => e.Message.Contains("MIGRATE20013::Non-Csharp App: Cannot migrate project")); + } + + private IEnumerable EnumerateFilesWithRelativePath(string testProjectDirectory) + { + return + Directory.EnumerateFiles(testProjectDirectory, "*", SearchOption.AllDirectories) + .Select(file => PathUtility.GetRelativePath(testProjectDirectory, file)); + } + + private class FakeEmptyMigrationRule : IMigrationRule + { + public void Apply(MigrationSettings migrationSettings, MigrationRuleInputs migrationRuleInputs) + { + return; + } + } + } +} \ No newline at end of file diff --git a/test/Microsoft.DotNet.ProjectJsonMigration.Tests/GivenMSBuildExtensions.cs b/test/Microsoft.DotNet.ProjectJsonMigration.Tests/GivenMSBuildExtensions.cs index bf2923a10..b93ff6778 100644 --- a/test/Microsoft.DotNet.ProjectJsonMigration.Tests/GivenMSBuildExtensions.cs +++ b/test/Microsoft.DotNet.ProjectJsonMigration.Tests/GivenMSBuildExtensions.cs @@ -15,6 +15,8 @@ namespace Microsoft.DotNet.ProjectJsonMigration.Tests item.Include = "include1;include2;aaa"; var includes = item.Includes().ToArray(); + + includes.Should().HaveCount(3); includes[0].Should().Be("include1"); includes[1].Should().Be("include2"); includes[2].Should().Be("aaa"); @@ -28,6 +30,8 @@ namespace Microsoft.DotNet.ProjectJsonMigration.Tests item.Exclude = "include1;include2;aaa"; var excludes = item.Excludes().ToArray(); + + excludes.Should().HaveCount(3); excludes[0].Should().Be("include1"); excludes[1].Should().Be("include2"); excludes[2].Should().Be("aaa"); @@ -74,7 +78,7 @@ namespace Microsoft.DotNet.ProjectJsonMigration.Tests { var project = ProjectRootElement.Create(); var item1 = project.AddItem("test", "include1;include2"); - item1.AddIncludes(new string[] {"include2", "include3"}); + item1.UnionIncludes(new string[] {"include2", "include3"}); item1.Include.Should().Be("include1;include2;include3"); } @@ -85,7 +89,7 @@ namespace Microsoft.DotNet.ProjectJsonMigration.Tests var project = ProjectRootElement.Create(); var item1 = project.AddItem("test", "include1"); item1.Exclude = "exclude1;exclude2"; - item1.AddExcludes(new string[] {"exclude2", "exclude3"}); + item1.UnionExcludes(new string[] {"exclude2", "exclude3"}); item1.Exclude.Should().Be("exclude1;exclude2;exclude3"); } @@ -98,7 +102,24 @@ namespace Microsoft.DotNet.ProjectJsonMigration.Tests item1.AddMetadata("name", "value"); item1.HasMetadata.Should().BeTrue(); - var item2 = project.AddItem("test1", "hey"); + var item2 = project.AddItem("test1", "include1"); + item2.AddMetadata(item1.Metadata); + + item2.HasMetadata.Should().BeTrue(); + item2.Metadata.First().Name.Should().Be("name"); + item2.Metadata.First().Value.Should().Be("value"); + } + + [Fact] + public void AddMetadata_adds_metadata_from_an_item_generated_from_another_project() + { + var project = ProjectRootElement.Create(); + var item1 = project.AddItem("test", "include1"); + item1.AddMetadata("name", "value"); + item1.HasMetadata.Should().BeTrue(); + + var project2 = ProjectRootElement.Create(); + var item2 = project2.AddItem("test1", "include1"); item2.AddMetadata(item1.Metadata); item2.HasMetadata.Should().BeTrue(); diff --git a/test/Microsoft.DotNet.ProjectJsonMigration.Tests/MSBuild.exe b/test/Microsoft.DotNet.ProjectJsonMigration.Tests/MSBuild.exe index e69de29bb..2b4d0f999 100644 --- a/test/Microsoft.DotNet.ProjectJsonMigration.Tests/MSBuild.exe +++ b/test/Microsoft.DotNet.ProjectJsonMigration.Tests/MSBuild.exe @@ -0,0 +1 @@ +https://github.com/Microsoft/msbuild/issues/927 \ No newline at end of file diff --git a/test/Microsoft.DotNet.ProjectJsonMigration.Tests/MSBuild.exe.config b/test/Microsoft.DotNet.ProjectJsonMigration.Tests/MSBuild.exe.config index 2b31011cf..2b4d0f999 100644 --- a/test/Microsoft.DotNet.ProjectJsonMigration.Tests/MSBuild.exe.config +++ b/test/Microsoft.DotNet.ProjectJsonMigration.Tests/MSBuild.exe.config @@ -1 +1 @@ -hey \ No newline at end of file +https://github.com/Microsoft/msbuild/issues/927 \ No newline at end of file diff --git a/test/Microsoft.DotNet.ProjectJsonMigration.Tests/Microsoft.DotNet.ProjectJsonMigration.Tests.xproj b/test/Microsoft.DotNet.ProjectJsonMigration.Tests/Microsoft.DotNet.ProjectJsonMigration.Tests.xproj index 04e81394b..c4813d88e 100644 --- a/test/Microsoft.DotNet.ProjectJsonMigration.Tests/Microsoft.DotNet.ProjectJsonMigration.Tests.xproj +++ b/test/Microsoft.DotNet.ProjectJsonMigration.Tests/Microsoft.DotNet.ProjectJsonMigration.Tests.xproj @@ -1,4 +1,4 @@ - + 14.0.25123 @@ -8,8 +8,8 @@ 1F2EF070-AC5F-4078-AFB0-65745AC691B9 Microsoft.DotNet.ProjectJsonMigration.Tests - .\obj - .\bin\ + ..\artifacts\bin\ 2.0 diff --git a/test/Microsoft.DotNet.ProjectJsonMigration.Tests/ProjectJsonBuilder.cs b/test/Microsoft.DotNet.ProjectJsonMigration.Tests/ProjectJsonBuilder.cs index 204aa2afc..bbf9237df 100644 --- a/test/Microsoft.DotNet.ProjectJsonMigration.Tests/ProjectJsonBuilder.cs +++ b/test/Microsoft.DotNet.ProjectJsonMigration.Tests/ProjectJsonBuilder.cs @@ -13,9 +13,7 @@ namespace Microsoft.DotNet.ProjectJsonMigration.Tests /// public class ProjectJsonBuilder { - private static readonly string s_defaultProjectJsonTestAsset = "TestAppWithRuntimeOptions"; - - private TestAssetsManager _testAssetsManager; + private readonly TestAssetsManager _testAssetsManager; private JObject _projectJson; private bool _baseDefined = false; @@ -41,11 +39,6 @@ namespace Microsoft.DotNet.ProjectJsonMigration.Tests return _projectJson; } - public ProjectJsonBuilder FromDefaultBase() - { - return FromTestAssetBase(s_defaultProjectJsonTestAsset); - } - public ProjectJsonBuilder FromTestAssetBase(string testAssetName) { var testProjectDirectory = _testAssetsManager.CreateTestInstance(testAssetName).Path; diff --git a/test/Microsoft.DotNet.ProjectJsonMigration.Tests/Rules/GivenThatIWantToMigrateBuildOptions.cs b/test/Microsoft.DotNet.ProjectJsonMigration.Tests/Rules/GivenThatIWantToMigrateBuildOptions.cs index 4408d9a8b..899a78204 100644 --- a/test/Microsoft.DotNet.ProjectJsonMigration.Tests/Rules/GivenThatIWantToMigrateBuildOptions.cs +++ b/test/Microsoft.DotNet.ProjectJsonMigration.Tests/Rules/GivenThatIWantToMigrateBuildOptions.cs @@ -8,6 +8,7 @@ using System.IO; using System.Linq; using Xunit; using FluentAssertions; +using Microsoft.DotNet.ProjectJsonMigration.Rules; using Microsoft.DotNet.ProjectModel.Files; namespace Microsoft.DotNet.ProjectJsonMigration.Tests @@ -68,7 +69,7 @@ namespace Microsoft.DotNet.ProjectJsonMigration.Tests } [Fact] - public void Migrating_EmitEntryPoint_true_populates_OutputType_and_TargetExt_fields() + public void Migrating_EmitEntryPoint_true_populates_OutputType_field() { var mockProj = RunBuildOptionsRuleOnPj(@" { @@ -78,10 +79,7 @@ namespace Microsoft.DotNet.ProjectJsonMigration.Tests }"); mockProj.Properties.Count(p => p.Name == "OutputType").Should().Be(1); - mockProj.Properties.Count(p => p.Name == "TargetExt").Should().Be(1); - mockProj.Properties.First(p => p.Name == "OutputType").Value.Should().Be("Exe"); - mockProj.Properties.First(p => p.Name == "TargetExt").Value.Should().Be(".dll"); } [Fact] @@ -362,7 +360,7 @@ namespace Microsoft.DotNet.ProjectJsonMigration.Tests [InlineData("compile", "Compile")] [InlineData("embed", "EmbeddedResource")] [InlineData("copyToOutput", "Content")] - private void Migrating_compile_include_exclude_Populates_compile_item( + private void Migrating_group_include_exclude_Populates_appropriate_ProjectItemElement( string group, string itemName) { @@ -404,6 +402,11 @@ namespace Microsoft.DotNet.ProjectJsonMigration.Tests foreach (var item in mockProj.Items.Where(i => i.ItemType.Equals(itemName, StringComparison.Ordinal))) { + if (item.ItemType == "Content") + { + item.Metadata.Count(m => m.Name == "CopyToOutputDirectory").Should().Be(1); + } + if (item.Include.Contains(@"src\file1.cs")) { item.Include.Should().Be(@"src\file1.cs;src\file2.cs"); diff --git a/test/Microsoft.DotNet.ProjectJsonMigration.Tests/Rules/GivenThatIWantToMigrateConfigurations.cs b/test/Microsoft.DotNet.ProjectJsonMigration.Tests/Rules/GivenThatIWantToMigrateConfigurations.cs index bdf0a54d3..fbd179b5e 100644 --- a/test/Microsoft.DotNet.ProjectJsonMigration.Tests/Rules/GivenThatIWantToMigrateConfigurations.cs +++ b/test/Microsoft.DotNet.ProjectJsonMigration.Tests/Rules/GivenThatIWantToMigrateConfigurations.cs @@ -10,6 +10,7 @@ using Microsoft.DotNet.Tools.Test.Utilities; using NuGet.Frameworks; using Xunit; using FluentAssertions; +using Microsoft.DotNet.ProjectJsonMigration.Rules; using Microsoft.DotNet.ProjectJsonMigration.Tests; namespace Microsoft.DotNet.ProjectJsonMigrationMigration.Tests @@ -32,7 +33,7 @@ namespace Microsoft.DotNet.ProjectJsonMigrationMigration.Tests }"); mockProj.Properties.Count( - prop => prop.Name == "OutputType" || prop.Name == "TargetExt" || prop.Name == "DebugType").Should().Be(3); + prop => prop.Name == "OutputType" || prop.Name == "DebugType").Should().Be(2); mockProj.Properties.First(p => p.Name == "OutputType") .Parent.Condition.Should() @@ -59,11 +60,11 @@ namespace Microsoft.DotNet.ProjectJsonMigrationMigration.Tests }"); mockProj.Properties.Count(property => - property.Name == "OutputType" || property.Name == "TargetExt" || property.Name == "DebugType") - .Should().Be(3); + property.Name == "OutputType" || property.Name == "DebugType") + .Should().Be(2); foreach (var property in mockProj.Properties.Where(property => - property.Name == "OutputType" || property.Name == "TargetExt" || property.Name == "DebugType")) + property.Name == "OutputType" || property.Name == "DebugType")) { property.Parent.Condition.Should().Be(string.Empty); } @@ -141,7 +142,7 @@ namespace Microsoft.DotNet.ProjectJsonMigrationMigration.Tests action.ShouldThrow() .WithMessage( - "Unable to migrate projects with excluded files in configurations."); + "MIGRATE20012::Configuration Exclude: Unable to migrate projects with excluded files in configurations."); } private ProjectRootElement RunConfigurationsRuleOnPj(string s, string testDirectory = null) { diff --git a/test/Microsoft.DotNet.ProjectJsonMigration.Tests/Rules/GivenThatIWantToMigrateProjectDependencies.cs b/test/Microsoft.DotNet.ProjectJsonMigration.Tests/Rules/GivenThatIWantToMigrateProjectDependencies.cs index 532a934f9..5cd730a76 100644 --- a/test/Microsoft.DotNet.ProjectJsonMigration.Tests/Rules/GivenThatIWantToMigrateProjectDependencies.cs +++ b/test/Microsoft.DotNet.ProjectJsonMigration.Tests/Rules/GivenThatIWantToMigrateProjectDependencies.cs @@ -10,6 +10,7 @@ using System.Linq; using System.Threading.Tasks; using Xunit; using FluentAssertions; +using Microsoft.DotNet.ProjectJsonMigration.Rules; namespace Microsoft.DotNet.Migration.Tests { @@ -60,7 +61,7 @@ namespace Microsoft.DotNet.Migration.Tests var projectReferences = mockProj.Items.Where(item => item.ItemType.Equals("ProjectReference", StringComparison.Ordinal)); projectReferences.Count().Should().Be(1); - projectReferences.First().Include.Should().Be("../TestLibrary/TestLibrary.csproj"); + projectReferences.First().Include.Should().Be(Path.Combine("..", "TestLibrary", "TestLibrary.csproj")); } [Fact] @@ -79,7 +80,7 @@ namespace Microsoft.DotNet.Migration.Tests Action action = () => new MigrateProjectDependenciesRule().Apply(testSettings, testInputs); action.ShouldThrow() - .WithMessage("Cannot migrate unresolved project dependency, please ensure restore has been run."); + .Where(e => e.Message.Contains("MIGRATE1014::Unresolved Dependency: Unresolved project dependency (TestLibrary)")); } } } diff --git a/test/Microsoft.DotNet.ProjectJsonMigration.Tests/Rules/GivenThatIWantToMigratePublishOptions.cs b/test/Microsoft.DotNet.ProjectJsonMigration.Tests/Rules/GivenThatIWantToMigratePublishOptions.cs index d9fe2544b..5d7e1d3a0 100644 --- a/test/Microsoft.DotNet.ProjectJsonMigration.Tests/Rules/GivenThatIWantToMigratePublishOptions.cs +++ b/test/Microsoft.DotNet.ProjectJsonMigration.Tests/Rules/GivenThatIWantToMigratePublishOptions.cs @@ -10,6 +10,7 @@ using Microsoft.DotNet.Tools.Test.Utilities; using NuGet.Frameworks; using Xunit; using FluentAssertions; +using Microsoft.DotNet.ProjectJsonMigration.Rules; namespace Microsoft.DotNet.ProjectJsonMigration.Tests { @@ -34,12 +35,10 @@ namespace Microsoft.DotNet.ProjectJsonMigration.Tests mockProj.Items.Count(i => i.ItemType.Equals("Content", StringComparison.Ordinal)).Should().Be(2); - // From ProjectReader #L725 (Both are empty) - var defaultIncludePatterns = Enumerable.Empty(); - var defaultExcludePatterns = ProjectFilesCollection.DefaultPublishExcludePatterns; - foreach (var item in mockProj.Items.Where(i => i.ItemType.Equals("Content", StringComparison.Ordinal))) { + item.Metadata.Count(m => m.Name == "CopyToPublishDirectory").Should().Be(1); + if (item.Include.Contains(@"src\file1.cs")) { item.Include.Should().Be(@"src\file1.cs;src\file2.cs"); diff --git a/test/Microsoft.DotNet.ProjectJsonMigration.Tests/Rules/GivenThatIWantToMigrateRuntimeOptions.cs b/test/Microsoft.DotNet.ProjectJsonMigration.Tests/Rules/GivenThatIWantToMigrateRuntimeOptions.cs index 8089fef95..744e259ea 100644 --- a/test/Microsoft.DotNet.ProjectJsonMigration.Tests/Rules/GivenThatIWantToMigrateRuntimeOptions.cs +++ b/test/Microsoft.DotNet.ProjectJsonMigration.Tests/Rules/GivenThatIWantToMigrateRuntimeOptions.cs @@ -14,6 +14,7 @@ using NuGet.Frameworks; using Newtonsoft.Json; using Newtonsoft.Json.Linq; using System.Collections.Generic; +using Microsoft.DotNet.ProjectJsonMigration.Rules; namespace Microsoft.DotNet.ProjectJsonMigration.Tests { diff --git a/test/Microsoft.DotNet.ProjectJsonMigration.Tests/Rules/GivenThatIWantToMigrateScripts.cs b/test/Microsoft.DotNet.ProjectJsonMigration.Tests/Rules/GivenThatIWantToMigrateScripts.cs index ceea3d2df..fd108769f 100644 --- a/test/Microsoft.DotNet.ProjectJsonMigration.Tests/Rules/GivenThatIWantToMigrateScripts.cs +++ b/test/Microsoft.DotNet.ProjectJsonMigration.Tests/Rules/GivenThatIWantToMigrateScripts.cs @@ -9,23 +9,25 @@ using Microsoft.DotNet.ProjectJsonMigration; using System; using System.IO; using Microsoft.Build.Construction; +using Microsoft.DotNet.ProjectJsonMigration.Rules; namespace Microsoft.DotNet.ProjectJsonMigration.Tests { public class GivenThatIWantToMigrateScripts : TestBase { [Theory] - [InlineData("compile:FullTargetFramework", "$(TargetFrameworkIdentifier)=$(TargetFrameworkVersion)")] + [InlineData("compile:FullTargetFramework", "$(TargetFrameworkIdentifier),Version=$(TargetFrameworkVersion)")] [InlineData("compile:Configuration", "$(Configuration)")] [InlineData("compile:OutputFile", "$(TargetPath)")] [InlineData("compile:OutputDir", "$(TargetDir)")] [InlineData("publish:ProjectPath", "$(MSBuildThisFileDirectory)")] [InlineData("publish:Configuration", "$(Configuration)")] [InlineData("publish:OutputPath", "$(TargetDir)")] - [InlineData("publish:FullTargetFramework", "$(TargetFrameworkIdentifier)=$(TargetFrameworkVersion)")] + [InlineData("publish:FullTargetFramework", "$(TargetFrameworkIdentifier),Version=$(TargetFrameworkVersion)")] [InlineData("project:Version", "$(Version)")] - [InlineData("project:Name", "$(MSBuildThisFileName)")] + [InlineData("project:Name", "$(AssemblyName)")] [InlineData("project:Directory", "$(MSBuildProjectDirectory)")] + [InlineData("publish:Runtime", "$(RuntimeIdentifier)")] public void Formatting_script_commands_replaces_variables_with_the_right_msbuild_properties( string variable, string msbuildReplacement) @@ -41,7 +43,6 @@ namespace Microsoft.DotNet.ProjectJsonMigration.Tests [InlineData("compile:RuntimeOutputDir")] [InlineData("compile:RuntimeIdentifier")] [InlineData("publish:TargetFramework")] - [InlineData("publish:Runtime")] public void Formatting_script_commands_throws_when_variable_is_unsupported(string unsupportedVariable) { var scriptMigrationRule = new MigrateScriptsRule(); @@ -158,7 +159,7 @@ namespace Microsoft.DotNet.ProjectJsonMigration.Tests { count += 1; var scriptExtensionProperties = - propertyGroup.Properties.Where(p => p.Name.Contains($"MigratedScriptExtension_{count}")).ToArray(); + propertyGroup.Properties.Where(p => p.Name.Contains($"MigratedScriptExtension_{scriptName}_{count}")).ToArray(); scriptExtensionProperties.All(p => p.Value == ".sh" || p.Value == ".cmd").Should().BeTrue(); scriptExtensionProperties.Count().Should().Be(2); diff --git a/test/Microsoft.DotNet.ProjectJsonMigration.Tests/Rules/GivenThatIWantToMigrateTFMs.cs b/test/Microsoft.DotNet.ProjectJsonMigration.Tests/Rules/GivenThatIWantToMigrateTFMs.cs index 2018aec96..237f7c4b3 100644 --- a/test/Microsoft.DotNet.ProjectJsonMigration.Tests/Rules/GivenThatIWantToMigrateTFMs.cs +++ b/test/Microsoft.DotNet.ProjectJsonMigration.Tests/Rules/GivenThatIWantToMigrateTFMs.cs @@ -9,6 +9,7 @@ using System.Linq; using System.Threading.Tasks; using Xunit; using FluentAssertions; +using Microsoft.DotNet.ProjectJsonMigration.Rules; namespace Microsoft.DotNet.ProjectJsonMigration.Tests { @@ -19,7 +20,7 @@ namespace Microsoft.DotNet.ProjectJsonMigration.Tests { var testDirectory = Temp.CreateDirectory().Path; var testPJ = new ProjectJsonBuilder(TestAssetsManager) - .FromDefaultBase() + .FromTestAssetBase("TestAppWithRuntimeOptions") .WithCustomProperty("buildOptions", new Dictionary { { "emitEntryPoint", "false" } diff --git a/test/Microsoft.DotNet.ProjectJsonMigration.Tests/TemporaryProjectFileRuleRunner.cs b/test/Microsoft.DotNet.ProjectJsonMigration.Tests/TemporaryProjectFileRuleRunner.cs index a89257594..7400ffc99 100644 --- a/test/Microsoft.DotNet.ProjectJsonMigration.Tests/TemporaryProjectFileRuleRunner.cs +++ b/test/Microsoft.DotNet.ProjectJsonMigration.Tests/TemporaryProjectFileRuleRunner.cs @@ -1,5 +1,6 @@ using System.Collections.Generic; using Microsoft.Build.Construction; +using Microsoft.DotNet.ProjectJsonMigration.Rules; using Microsoft.DotNet.ProjectModel; using NuGet.Frameworks; diff --git a/test/Microsoft.DotNet.ProjectJsonMigration.Tests/Transforms/GivenAConditionalTransform.cs b/test/Microsoft.DotNet.ProjectJsonMigration.Tests/Transforms/GivenAConditionalTransform.cs index a36c23817..c74fb6b04 100644 --- a/test/Microsoft.DotNet.ProjectJsonMigration.Tests/Transforms/GivenAConditionalTransform.cs +++ b/test/Microsoft.DotNet.ProjectJsonMigration.Tests/Transforms/GivenAConditionalTransform.cs @@ -6,6 +6,7 @@ using System.Threading.Tasks; using Xunit; using FluentAssertions; using Microsoft.Build.Construction; +using Microsoft.DotNet.ProjectJsonMigration.Transforms; namespace Microsoft.DotNet.ProjectJsonMigration.Tests { diff --git a/test/Microsoft.DotNet.ProjectJsonMigration.Tests/Transforms/GivenATransformApplicator.cs b/test/Microsoft.DotNet.ProjectJsonMigration.Tests/Transforms/GivenATransformApplicator.cs index cc348a2ad..b68024f75 100644 --- a/test/Microsoft.DotNet.ProjectJsonMigration.Tests/Transforms/GivenATransformApplicator.cs +++ b/test/Microsoft.DotNet.ProjectJsonMigration.Tests/Transforms/GivenATransformApplicator.cs @@ -4,6 +4,8 @@ using Xunit; using Xunit.Runner.DotNet; using FluentAssertions; using System.Linq; +using Microsoft.DotNet.ProjectJsonMigration.Models; +using Microsoft.DotNet.ProjectJsonMigration.Transforms; namespace Microsoft.DotNet.ProjectJsonMigration.Tests.Transforms { @@ -23,15 +25,13 @@ namespace Microsoft.DotNet.ProjectJsonMigration.Tests.Transforms var transform1 = new AddItemTransform("item", fullItemTransformSetIncludeValue, "exclude1", - t => true, - mergeExisting: true) + t => true) .WithMetadata(metadata[0]); var transform2 = new AddItemTransform("item", fullItemTransformSetIncludeValue, "exclude2", - t => true, - mergeExisting: true) + t => true) .WithMetadata(metadata[1]); var mockProj = ProjectRootElement.Create(); @@ -64,26 +64,5 @@ namespace Microsoft.DotNet.ProjectJsonMigration.Tests.Transforms foundMetadata.All(kv => kv.Value).Should().BeTrue(); } - -// [Fact] -// public void It_adds_duplicate_properties_to_the_project_with_specified_value_when_the_property_exists() -// { -// var mockProj = ProjectRootElement.Create(); -// var propertyGroup = mockProj.AddPropertyGroup(); -// var propertyName = "Property1"; -// var propertyValue = "Value1"; -// -// var propertyTransform = new AddPropertyTransform(propertyName, propertyValue, t => true); -// propertyTransform.Transform("_"); -// propertyTransform.Transform("_", mockProj, propertyGroup); -// -// propertyGroup.Properties.Count.Should().Be(2); -// -// foreach (var property in propertyGroup.Properties) -// { -// property.Name.Should().Be(propertyName); -// property.Value.Should().Be(propertyValue); -// } -// } } } \ No newline at end of file diff --git a/test/Microsoft.DotNet.ProjectJsonMigration.Tests/Transforms/GivenAnAddBoolPropertyTransform.cs b/test/Microsoft.DotNet.ProjectJsonMigration.Tests/Transforms/GivenAnAddBoolPropertyTransform.cs deleted file mode 100644 index 9d30d4284..000000000 --- a/test/Microsoft.DotNet.ProjectJsonMigration.Tests/Transforms/GivenAnAddBoolPropertyTransform.cs +++ /dev/null @@ -1,39 +0,0 @@ -using Microsoft.Build.Construction; -using Microsoft.DotNet.ProjectJsonMigration; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; -using Xunit; -using FluentAssertions; - -namespace Microsoft.DotNet.Migration.Tests -{ - public class GivenAnAddBoolPropertyTransform - { - [Theory] - [InlineData(true)] - [InlineData(false)] - public void It_returns_a_property_to_the_project_with_boolean_value(bool propertyValue) - { - var propertyName = "Property1"; - - var propertyTransform = new AddBoolPropertyTransform(propertyName, t => true); - var property = propertyTransform.Transform(propertyValue); - - property.Name.Should().Be(propertyName); - property.Value.Should().Be(propertyValue.ToString()); - } - - [Theory] - [InlineData(true)] - [InlineData(false)] - public void It_returns_null_when_condition_is_false(bool propertyValue) - { - var propertyName = "Property1"; - - var propertyTransform = new AddBoolPropertyTransform(propertyName, t => false); - propertyTransform.Transform(propertyValue).Should().BeNull(); - } - } -} diff --git a/test/Microsoft.DotNet.ProjectJsonMigration.Tests/Transforms/GivenAnAddItemTransform.cs b/test/Microsoft.DotNet.ProjectJsonMigration.Tests/Transforms/GivenAnAddItemTransform.cs index 0ea9c4cef..86113f10d 100644 --- a/test/Microsoft.DotNet.ProjectJsonMigration.Tests/Transforms/GivenAnAddItemTransform.cs +++ b/test/Microsoft.DotNet.ProjectJsonMigration.Tests/Transforms/GivenAnAddItemTransform.cs @@ -6,6 +6,8 @@ using System.Linq; using System.Threading.Tasks; using Xunit; using FluentAssertions; +using Microsoft.DotNet.ProjectJsonMigration.Models; +using Microsoft.DotNet.ProjectJsonMigration.Transforms; namespace Microsoft.DotNet.Migration.Tests { diff --git a/test/Microsoft.DotNet.ProjectJsonMigration.Tests/Transforms/GivenAnAddPropertyTransform.cs b/test/Microsoft.DotNet.ProjectJsonMigration.Tests/Transforms/GivenAnAddPropertyTransform.cs index 6d497a522..ac6be55f9 100644 --- a/test/Microsoft.DotNet.ProjectJsonMigration.Tests/Transforms/GivenAnAddPropertyTransform.cs +++ b/test/Microsoft.DotNet.ProjectJsonMigration.Tests/Transforms/GivenAnAddPropertyTransform.cs @@ -6,6 +6,7 @@ using System.Linq; using System.Threading.Tasks; using Xunit; using FluentAssertions; +using Microsoft.DotNet.ProjectJsonMigration.Transforms; namespace Microsoft.DotNet.Migration.Tests { diff --git a/test/Microsoft.DotNet.ProjectJsonMigration.Tests/Transforms/GivenAnAddStringPropertyTransform.cs b/test/Microsoft.DotNet.ProjectJsonMigration.Tests/Transforms/GivenAnAddStringPropertyTransform.cs deleted file mode 100644 index 17784f1c8..000000000 --- a/test/Microsoft.DotNet.ProjectJsonMigration.Tests/Transforms/GivenAnAddStringPropertyTransform.cs +++ /dev/null @@ -1,57 +0,0 @@ -using Microsoft.Build.Construction; -using Microsoft.DotNet.ProjectJsonMigration; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; -using Xunit; -using FluentAssertions; - -namespace Microsoft.DotNet.Migration.Tests -{ - public class GivenAnAddStringPropertyTransform - { - [Fact] - public void It_adds_a_property_to_the_project_with_string_value() - { - var propertyName = "Property1"; - var propertyValue = "TestValue1"; - - var propertyTransform = new AddStringPropertyTransform(propertyName, t => true); - var property = propertyTransform.Transform(propertyValue); - property.Name.Should().Be(propertyName); - property.Value.Should().Be(propertyValue); - } - - [Fact] - public void It_returns_null_when_propertyValue_is_null_and_condition_is_passed() - { - var propertyName = "Property1"; - string propertyValue = null; - - var propertyTransform = new AddStringPropertyTransform(propertyName, t => true); - propertyTransform.Transform(propertyValue).Should().BeNull(); - } - [Fact] - public void It_returns_null_when_propertyValue_is_null_and_condition_is_not_passed() - { - var mockProj = ProjectRootElement.Create(); - var propertyName = "Property1"; - string propertyValue = null; - - var propertyTransform = new AddStringPropertyTransform(propertyName); - propertyTransform.Transform(propertyValue).Should().BeNull(); - } - - - [Fact] - public void It_returns_null_when_condition_is_false() - { - var propertyName = "Property1"; - var propertyValue = "TestValue1"; - - var propertyTransform = new AddStringPropertyTransform(propertyName, t => false); - propertyTransform.Transform(propertyValue).Should().BeNull(); - } - } -} diff --git a/test/Microsoft.DotNet.ProjectJsonMigration.Tests/Transforms/GivenAnIncludeContextTransformation.cs b/test/Microsoft.DotNet.ProjectJsonMigration.Tests/Transforms/GivenAnIncludeContextTransformation.cs index f15b0036c..454650208 100644 --- a/test/Microsoft.DotNet.ProjectJsonMigration.Tests/Transforms/GivenAnIncludeContextTransformation.cs +++ b/test/Microsoft.DotNet.ProjectJsonMigration.Tests/Transforms/GivenAnIncludeContextTransformation.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; +using Microsoft.DotNet.ProjectJsonMigration.Transforms; namespace Microsoft.DotNet.Migration.Tests { diff --git a/test/Microsoft.DotNet.Tools.Tests.Utilities/Commands/NewCommand.cs b/test/Microsoft.DotNet.Tools.Tests.Utilities/Commands/NewCommand.cs index 35bb30ccd..951982dde 100644 --- a/test/Microsoft.DotNet.Tools.Tests.Utilities/Commands/NewCommand.cs +++ b/test/Microsoft.DotNet.Tools.Tests.Utilities/Commands/NewCommand.cs @@ -18,5 +18,11 @@ namespace Microsoft.DotNet.Tools.Test.Utilities args = $"new {args}"; return base.Execute(args); } + + public override CommandResult ExecuteWithCapturedOutput(string args = "") + { + args = $"new {args}"; + return base.ExecuteWithCapturedOutput(args); + } } } diff --git a/test/Microsoft.DotNet.ProjectJsonMigration.Tests/Scenarios/GivenThatIWantToMigrateTestApps.cs b/test/dotnet-migrate.Tests/GivenThatIWantToMigrateTestApps.cs similarity index 52% rename from test/Microsoft.DotNet.ProjectJsonMigration.Tests/Scenarios/GivenThatIWantToMigrateTestApps.cs rename to test/dotnet-migrate.Tests/GivenThatIWantToMigrateTestApps.cs index dbb2b7017..b541bb8dc 100644 --- a/test/Microsoft.DotNet.ProjectJsonMigration.Tests/Scenarios/GivenThatIWantToMigrateTestApps.cs +++ b/test/dotnet-migrate.Tests/GivenThatIWantToMigrateTestApps.cs @@ -10,61 +10,65 @@ using System.Threading.Tasks; using Xunit; using FluentAssertions; using System.IO; +using System.Runtime.InteropServices; using Microsoft.DotNet.Tools.Common; using Microsoft.DotNet.Cli; -using Microsoft.DotNet.Cli.Utils; +using Microsoft.DotNet.Tools.Migrate; using Build3Command = Microsoft.DotNet.Tools.Test.Utilities.Build3Command; -using TemporaryDotnetNewTemplateProject = Microsoft.DotNet.Cli.TemporaryDotnetNewTemplateProject; namespace Microsoft.DotNet.Migration.Tests { public class GivenThatIWantToMigrateTestApps : TestBase { - private class Failure - { - public string Phase {get; set;} - public string Message {get; set;} - public string ProjectJson {get; set;} - } - [Theory] // TODO: Standalone apps [InlineData("TestAppSimple", false)] // https://github.com/dotnet/sdk/issues/73 [InlineData("TestAppWithLibrary/TestApp", false)] - [InlineData("TestAppWithRuntimeOptions", false)] - public void It_migrates_a_project(string projectName, bool isLibrary) + [InlineData("TestAppWithRuntimeOptions")] + public void It_migrates_apps(string projectName) { var projectDirectory = TestAssetsManager.CreateTestInstance(projectName, callingMethod: "i").WithLockFiles().Path; + var outputComparisonData = BuildProjectJsonMigrateBuildMSBuild(projectDirectory); - BuildProjectJson(projectDirectory); - var projectJsonBuildOutputs = new HashSet(CollectBuildOutputs(projectDirectory)); - CleanBinObj(projectDirectory); - - MigrateProject(projectDirectory); - Restore(projectDirectory); - BuildMSBuild(projectDirectory); - - var msbuildBuildOutputs = new HashSet(CollectBuildOutputs(projectDirectory)); - - var outputsIdentical = projectJsonBuildOutputs.SetEquals(msbuildBuildOutputs); - - // diagnostics + var outputsIdentical = + outputComparisonData.ProjectJsonBuildOutputs.SetEquals(outputComparisonData.MSBuildBuildOutputs); if (!outputsIdentical) { - Console.WriteLine("Project.json Outputs:"); - Console.WriteLine(string.Join("\n", projectJsonBuildOutputs)); - - Console.WriteLine(""); - - Console.WriteLine("MSBuild Outputs:"); - Console.WriteLine(string.Join("\n", msbuildBuildOutputs)); + OutputDiagnostics(outputComparisonData); } - outputsIdentical.Should().BeTrue(); + VerifyAllMSBuildOutputsRunnable(projectDirectory); + } - if (!isLibrary) + [Fact] + public void It_migrates_dotnet_new_console_with_identical_outputs() + { + var projectDirectory = Temp.CreateDirectory().Path; + var outputComparisonData = GetDotnetNewComparisonData(projectDirectory, "console"); + + var outputsIdentical = + outputComparisonData.ProjectJsonBuildOutputs.SetEquals(outputComparisonData.MSBuildBuildOutputs); + if (!outputsIdentical) { - VerifyMSBuildOutputRunnable(projectDirectory); + OutputDiagnostics(outputComparisonData); } + outputsIdentical.Should().BeTrue(); + VerifyAllMSBuildOutputsRunnable(projectDirectory); + } + + [Fact] + // Outputs Not Identical: https://github.com/dotnet/sdk/issues/97 + public void It_migrates_dotnet_new_web_with_outputs_containing_project_json_outputs() + { + var projectDirectory = Temp.CreateDirectory().Path; + var outputComparisonData = GetDotnetNewComparisonData(projectDirectory, "web"); + + var projectJsonOutputIsSubsetOfMSBuildOutput = + outputComparisonData.ProjectJsonBuildOutputs.IsProperSubsetOf(outputComparisonData.MSBuildBuildOutputs); + if (!projectJsonOutputIsSubsetOfMSBuildOutput) + { + OutputDiagnostics(outputComparisonData); + } + projectJsonOutputIsSubsetOfMSBuildOutput.Should().BeTrue(); } [Theory] @@ -75,29 +79,14 @@ namespace Microsoft.DotNet.Migration.Tests { var projectDirectory = TestAssetsManager.CreateTestInstance(projectName, callingMethod: "i").WithLockFiles().Path; + var outputComparisonData = BuildProjectJsonMigrateBuildMSBuild(projectDirectory); - BuildProjectJson(projectDirectory); - var projectJsonBuildOutputs = new HashSet(CollectBuildOutputs(projectDirectory)); - CleanBinObj(projectDirectory); + var msBuildHasAdditionalOutputsButIncludesProjectJsonOutputs = + outputComparisonData.ProjectJsonBuildOutputs.IsProperSubsetOf(outputComparisonData.MSBuildBuildOutputs); - MigrateProject(projectDirectory); - Restore(projectDirectory); - BuildMSBuild(projectDirectory); - - var msbuildBuildOutputs = new HashSet(CollectBuildOutputs(projectDirectory)); - - var msBuildHasAdditionalOutputsButIncludesProjectJsonOutputs = projectJsonBuildOutputs.IsProperSubsetOf(msbuildBuildOutputs); - - // diagnostics if (!msBuildHasAdditionalOutputsButIncludesProjectJsonOutputs) { - Console.WriteLine("Project.json Outputs:"); - Console.WriteLine(string.Join("\n", projectJsonBuildOutputs)); - - Console.WriteLine(""); - - Console.WriteLine("MSBuild Outputs:"); - Console.WriteLine(string.Join("\n", msbuildBuildOutputs)); + OutputDiagnostics(outputComparisonData); } msBuildHasAdditionalOutputsButIncludesProjectJsonOutputs.Should().BeTrue(); @@ -107,7 +96,7 @@ namespace Microsoft.DotNet.Migration.Tests public void It_migrates_an_app_with_scripts_and_the_scripts_run() { var projectDirectory = - TestAssetsManager.CreateTestInstance("TestAppWithMigrateAbleScripts", callingMethod: "i").WithLockFiles().Path; + TestAssetsManager.CreateTestInstance("TestAppWithMigrateableScripts", callingMethod: "i").WithLockFiles().Path; BuildProjectJson(projectDirectory); var projectJsonBuildOutputs = new HashSet(CollectBuildOutputs(projectDirectory)); @@ -121,26 +110,25 @@ namespace Microsoft.DotNet.Migration.Tests var outputsIdentical = projectJsonBuildOutputs.SetEquals(msbuildBuildOutputs); outputsIdentical.Should().BeTrue(); - VerifyMSBuildOutputRunnable(projectDirectory); + VerifyAllMSBuildOutputsRunnable(projectDirectory); var outputDir = PathUtility.EnsureTrailingSlash(Path.Combine(projectDirectory, "bin", "Debug", "netcoreapp1.0")); - msBuildStdOut.Should().Contain($"precompile_output ?Debug? ?{outputDir}? ?.NETCoreApp=v1.0?"); - msBuildStdOut.Should().Contain($"postcompile_output ?Debug? ?{outputDir}? ?.NETCoreApp=v1.0?"); + msBuildStdOut.Should().Contain($"precompile_output ?Debug? ?{outputDir}? ?.NETCoreApp,Version=v1.0?"); + msBuildStdOut.Should().Contain($"postcompile_output ?Debug? ?{outputDir}? ?.NETCoreApp,Version=v1.0?"); } - private string RunNetcoreappMSBuildOutput(string projectDirectory) + private MigratedBuildComparisonData GetDotnetNewComparisonData(string projectDirectory, string dotnetNewType) { - var dllFileName = Path.GetFileName(projectDirectory) + ".dll"; + DotnetNew(projectDirectory, dotnetNewType); + Restore(projectDirectory); - var runnableDll = Path.Combine(projectDirectory, "bin","Debug", "netcoreapp1.0", dllFileName); - var result = new TestCommand("dotnet").ExecuteWithCapturedOutput(runnableDll); - result.Should().Pass(); - return result.StdOut; + var outputComparisonData = BuildProjectJsonMigrateBuildMSBuild(projectDirectory); + return outputComparisonData; } - private void VerifyMSBuildOutputRunnable(string projectDirectory) + private void VerifyAllMSBuildOutputsRunnable(string projectDirectory) { var dllFileName = Path.GetFileName(projectDirectory) + ".dll"; @@ -153,6 +141,21 @@ namespace Microsoft.DotNet.Migration.Tests } } + private MigratedBuildComparisonData BuildProjectJsonMigrateBuildMSBuild(string projectDirectory) + { + BuildProjectJson(projectDirectory); + var projectJsonBuildOutputs = new HashSet(CollectBuildOutputs(projectDirectory)); + CleanBinObj(projectDirectory); + + MigrateProject(projectDirectory); + Restore(projectDirectory); + BuildMSBuild(projectDirectory); + + var msbuildBuildOutputs = new HashSet(CollectBuildOutputs(projectDirectory)); + + return new MigratedBuildComparisonData(projectJsonBuildOutputs, msbuildBuildOutputs); + } + private IEnumerable CollectBuildOutputs(string projectDirectory) { var fullBinPath = Path.GetFullPath(Path.Combine(projectDirectory, "bin")); @@ -182,10 +185,18 @@ namespace Microsoft.DotNet.Migration.Tests private void MigrateProject(string projectDirectory) { - var dotnetNew = new TemporaryDotnetNewTemplateProject(); - var sdkVersion = new ProjectJsonParser(dotnetNew.ProjectJson).SdkPackageVersion; - var migrationSettings = new MigrationSettings(projectDirectory, projectDirectory, sdkVersion, dotnetNew.MSBuildProject); - new ProjectMigrator().Migrate(migrationSettings); + var result = + MigrateCommand.Run(new [] { "-p", projectDirectory }); + + result.Should().Be(0); + } + + private void DotnetNew(string projectDirectory, string dotnetNewType) + { + new NewCommand().WithWorkingDirectory(projectDirectory) + .ExecuteWithCapturedOutput($"-t {dotnetNewType}") + .Should() + .Pass(); } private void Restore(string projectDirectory) @@ -197,16 +208,46 @@ namespace Microsoft.DotNet.Migration.Tests .Pass(); } - private string BuildMSBuild(string projectDirectory) + private string BuildMSBuild(string projectDirectory, string configuration="Debug") { var result = new Build3Command() .WithWorkingDirectory(projectDirectory) - .ExecuteWithCapturedOutput(); + .ExecuteWithCapturedOutput($"/p:Configuration={configuration}"); + result .Should() .Pass(); return result.StdOut; } + + private void OutputDiagnostics(MigratedBuildComparisonData comparisonData) + { + OutputDiagnostics(comparisonData.MSBuildBuildOutputs, comparisonData.ProjectJsonBuildOutputs); + } + + private void OutputDiagnostics(HashSet msbuildBuildOutputs, HashSet projectJsonBuildOutputs) + { + Console.WriteLine("Project.json Outputs:"); + Console.WriteLine(string.Join("\n", projectJsonBuildOutputs)); + + Console.WriteLine(""); + + Console.WriteLine("MSBuild Outputs:"); + Console.WriteLine(string.Join("\n", msbuildBuildOutputs)); + } + + private class MigratedBuildComparisonData + { + public HashSet ProjectJsonBuildOutputs { get; } + public HashSet MSBuildBuildOutputs { get; } + + public MigratedBuildComparisonData(HashSet projectJsonBuildOutputs, + HashSet msBuildBuildOutputs) + { + ProjectJsonBuildOutputs = projectJsonBuildOutputs; + MSBuildBuildOutputs = msBuildBuildOutputs; + } + } } } diff --git a/test/dotnet-migrate.Tests/MSBuild.exe b/test/dotnet-migrate.Tests/MSBuild.exe new file mode 100644 index 000000000..2b4d0f999 --- /dev/null +++ b/test/dotnet-migrate.Tests/MSBuild.exe @@ -0,0 +1 @@ +https://github.com/Microsoft/msbuild/issues/927 \ No newline at end of file diff --git a/test/dotnet-migrate.Tests/MSBuild.exe.config b/test/dotnet-migrate.Tests/MSBuild.exe.config new file mode 100644 index 000000000..2b4d0f999 --- /dev/null +++ b/test/dotnet-migrate.Tests/MSBuild.exe.config @@ -0,0 +1 @@ +https://github.com/Microsoft/msbuild/issues/927 \ No newline at end of file diff --git a/test/dotnet-migrate.Tests/dotnet-migrate.Tests.xproj b/test/dotnet-migrate.Tests/dotnet-migrate.Tests.xproj new file mode 100644 index 000000000..04e81394b --- /dev/null +++ b/test/dotnet-migrate.Tests/dotnet-migrate.Tests.xproj @@ -0,0 +1,21 @@ + + + + 14.0.25123 + $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) + + + + 1F2EF070-AC5F-4078-AFB0-65745AC691B9 + Microsoft.DotNet.ProjectJsonMigration.Tests + .\obj + .\bin\ + + + 2.0 + + + + + + \ No newline at end of file diff --git a/test/dotnet-migrate.Tests/project.json b/test/dotnet-migrate.Tests/project.json new file mode 100644 index 000000000..a7b61f903 --- /dev/null +++ b/test/dotnet-migrate.Tests/project.json @@ -0,0 +1,39 @@ +{ + "version": "1.0.0-*", + "buildOptions": { + "copyToOutput": ["MSBuild.exe", "MSBuild.exe.config"] + }, + "dependencies": { + "Microsoft.NETCore.App": { + "type": "platform", + "version": "1.0.0" + }, + "Microsoft.DotNet.ProjectJsonMigration": { + "target": "project" + }, + "xunit": "2.2.0-beta3-build3330", + "dotnet-test-xunit": "1.0.0-rc2-318883-21", + "FluentAssertions": "4.0.0", + "moq.netcore": "4.4.0-beta8", + "Microsoft.DotNet.Tools.Tests.Utilities": { + "target": "project" + }, + "Microsoft.DotNet.Cli.Utils": { + "target": "project" + }, + "dotnet": { + "target":"project" + } + }, + + "frameworks": { + "netcoreapp1.0": { + "imports": [ + "netstandardapp1.5", + "dotnet5.4", + "portable-net451+win8" + ] + } + }, + "testRunner": "xunit" +}