diff --git a/.gitignore b/.gitignore index b06878e45..926f18be7 100644 --- a/.gitignore +++ b/.gitignore @@ -358,4 +358,6 @@ MigrationBackup/ **/*/launchSettings.json **/*/FolderProfile.pubxml -.tools/* \ No newline at end of file +.tools/* + +.vscode/ \ No newline at end of file diff --git a/src/Directory.Build.props b/src/Directory.Build.props index c188561ac..894909b4e 100644 --- a/src/Directory.Build.props +++ b/src/Directory.Build.props @@ -10,7 +10,7 @@ - 8.0 + preview enable true diff --git a/src/MSBuild.Abstractions/BaselineProject.cs b/src/MSBuild.Abstractions/BaselineProject.cs index 1c7609ea5..87ddd0334 100644 --- a/src/MSBuild.Abstractions/BaselineProject.cs +++ b/src/MSBuild.Abstractions/BaselineProject.cs @@ -27,12 +27,12 @@ public BaselineProject(UnconfiguredProject project, ImmutableArray globa private static string AdjustTargetTFM(ProjectStyle projectStyle, ProjectOutputType outputType, string candidateTargetTFM) { - if (candidateTargetTFM.ContainsIgnoreCase(MSBuildFacts.Net5) && projectStyle == ProjectStyle.WindowsDesktop) + if (candidateTargetTFM.ContainsIgnoreCase(MSBuildFacts.Net5) && projectStyle is ProjectStyle.WindowsDesktop) { return MSBuildFacts.Net5Windows; } - if (outputType == ProjectOutputType.Library) + if (projectStyle is not ProjectStyle.MSTest && outputType is ProjectOutputType.Library) { return MSBuildFacts.NetStandard20; } diff --git a/src/MSBuild.Abstractions/MSBuildConversionWorkspace.cs b/src/MSBuild.Abstractions/MSBuildConversionWorkspace.cs index c379a6129..b6828baf5 100644 --- a/src/MSBuild.Abstractions/MSBuildConversionWorkspace.cs +++ b/src/MSBuild.Abstractions/MSBuildConversionWorkspace.cs @@ -3,6 +3,7 @@ using System.Collections.Immutable; using System.IO; using System.Linq; +using System.Diagnostics.CodeAnalysis; using Microsoft.Build.Construction; using Microsoft.Build.Evaluation; @@ -36,24 +37,27 @@ public MSBuildConversionWorkspace(ImmutableArray paths, bool noBackup, s var root = new MSBuildProjectRootElement(ProjectRootElement.Open(path, collection, preserveFormatting: true)); if (IsSupportedProjectType(root)) { - if (!noBackup) - { - // Since git doesn't track the new '.old' addition in your changeset, - // failing to overwrite will crash the tool if you have one in your directory. - // This can be common if you're using the tool a few times and forget to delete the backup. - File.Copy(path, path + ".old", overwrite: true); - } var configurations = DetermineConfigurations(root); var unconfiguredProject = new UnconfiguredProject(configurations); unconfiguredProject.LoadProjects(collection, globalProperties, path); - var baseline = CreateSdkBaselineProject(path, unconfiguredProject.FirstConfiguredProject, root, configurations, tfm, keepCurrentTFMs); - root.Reload(throwIfUnsavedChanges: false, preserveFormatting: true); - var item = new MSBuildConversionWorkspaceItem(root, unconfiguredProject, baseline); - items.Add(item); + if (TryCreateSdkBaselineProject(path, unconfiguredProject.FirstConfiguredProject, root, configurations, tfm, keepCurrentTFMs, out var baseline)) + { + if (!noBackup) + { + // Since git doesn't track the new '.old' addition in your changeset, + // failing to overwrite will crash the tool if you have one in your directory. + // This can be common if you're using the tool a few times and forget to delete the backup. + File.Copy(path, path + ".old", overwrite: true); + } + + root.Reload(throwIfUnsavedChanges: false, preserveFormatting: true); + var item = new MSBuildConversionWorkspaceItem(root, unconfiguredProject, baseline.Value); + items.Add(item); + } } } @@ -79,7 +83,7 @@ public ImmutableDictionary> Determin builder.Add(dimensionValuePair.Value, dimensionValueDictionary.ToImmutableDictionary()); } } - } + } } } return builder.ToImmutable(); @@ -90,7 +94,7 @@ public ImmutableDictionary> Determin /// We need to use the same name as the original csproj and same path so that all the default that derive /// from name\path get the right values (there are a lot of them). /// - private BaselineProject CreateSdkBaselineProject(string projectFilePath, IProject project, IProjectRootElement root, ImmutableDictionary> configurations, string tfm, bool keepCurrentTFMs) + private bool TryCreateSdkBaselineProject(string projectFilePath, IProject project, IProjectRootElement root, ImmutableDictionary> configurations, string tfm, bool keepCurrentTFMs, [NotNullWhen(true)] out BaselineProject? baselineProject) { var projectStyle = GetProjectStyle(root); var outputType = GetProjectOutputType(root); @@ -111,7 +115,8 @@ private BaselineProject CreateSdkBaselineProject(string projectFilePath, IProjec : DesktopFacts.WinSDKAttribute; // pre-.NET 5 apps need a special SDK attribute. break; default: - throw new NotSupportedException($"This project has custom imports in a manner that's not supported. '{projectFilePath}'"); + baselineProject = null; + return false; } var propGroup = rootElement.AddPropertyGroup(); @@ -166,7 +171,8 @@ private BaselineProject CreateSdkBaselineProject(string projectFilePath, IProjec ? MSBuildFacts.Net5Windows : tfm; - return new BaselineProject(newProject, propertiesInTheBaseline, projectStyle, outputType, tfm, keepCurrentTFMs); + baselineProject = new BaselineProject(newProject, propertiesInTheBaseline, projectStyle, outputType, tfm, keepCurrentTFMs); + return true; } private bool IsSupportedOutputType(ProjectOutputType type) => @@ -231,21 +237,13 @@ private ProjectStyle GetProjectStyle(IProjectRootElement projectRootElement) } else { - var lastImport = imports.Last(); - var lastImportFileName = Path.GetFileName(lastImport.Project); - - if (firstImportFileName == FSharpFacts.FSharpTargetsPathVariableName) - { - firstImportFileName = Path.GetFileName(FSharpFacts.FSharpTargetsPath); - } - - if (lastImportFileName == FSharpFacts.FSharpTargetsPathVariableName) - { - lastImportFileName = Path.GetFileName(FSharpFacts.FSharpTargetsPath); - } + var cleansedImports = imports.Select(import => Path.GetFileName(import.Project)); + var allImportsConvertibleToSdk = + cleansedImports.All(import => + MSBuildFacts.PropsConvertibleToSDK.Contains(import, StringComparer.OrdinalIgnoreCase) || + MSBuildFacts.TargetsConvertibleToSDK.Contains(import, StringComparer.OrdinalIgnoreCase)); - if (MSBuildFacts.PropsConvertibleToSDK.Contains(firstImportFileName, StringComparer.OrdinalIgnoreCase) && - MSBuildFacts.TargetsConvertibleToSDK.Contains(lastImportFileName, StringComparer.OrdinalIgnoreCase)) + if (allImportsConvertibleToSdk) { if (MSBuildHelpers.IsNETFrameworkMSTestProject(projectRootElement)) { @@ -262,6 +260,26 @@ private ProjectStyle GetProjectStyle(IProjectRootElement projectRootElement) } else { + Console.WriteLine("This project has custom imports that are not accepted by try-convert."); + Console.WriteLine("Unexpected custom imports were found:"); + + var customImports = + cleansedImports.Where(import => + !(MSBuildFacts.PropsConvertibleToSDK.Contains(import, StringComparer.OrdinalIgnoreCase) || + MSBuildFacts.TargetsConvertibleToSDK.Contains(import, StringComparer.OrdinalIgnoreCase))); + + foreach (var import in customImports) + { + Console.WriteLine($"\t{import}"); + } + + Console.WriteLine("The following imports are considered valid for conversion:"); + + foreach (var import in MSBuildFacts.TargetsConvertibleToSDK.Union(MSBuildFacts.PropsConvertibleToSDK)) + { + Console.WriteLine($"\t{import}"); + } + // It's something else, no idea what though return ProjectStyle.Custom; } @@ -327,7 +345,7 @@ private bool IsSupportedProjectType(IProjectRootElement root) { return true; } - } + } static void PrintGuidMessage(IEnumerable allSupportedProjectTypeGuids, IEnumerable allReadProjectTypeGuids) { diff --git a/src/MSBuild.Conversion.Facts/MSBuildFacts.cs b/src/MSBuild.Conversion.Facts/MSBuildFacts.cs index 00428ad21..42d76244c 100644 --- a/src/MSBuild.Conversion.Facts/MSBuildFacts.cs +++ b/src/MSBuild.Conversion.Facts/MSBuildFacts.cs @@ -26,7 +26,8 @@ public static class MSBuildFacts "Microsoft.Portable.CSharp.targets", "Microsoft.Portable.VisualBasic.targets", "Microsoft.FSharp.Targets", - "MSTest.TestAdapter.targets" + "MSTest.TestAdapter.targets", + "Microsoft.TestTools.targets" ); /// diff --git a/src/try-convert/Program.cs b/src/try-convert/Program.cs index 129e94ccd..e66ea8e5c 100644 --- a/src/try-convert/Program.cs +++ b/src/try-convert/Program.cs @@ -101,6 +101,12 @@ public static int Run(string? project, string? workspace, string? msbuildPath, s noBackup = noBackup || diffOnly; var msbuildWorkspace = workspaceLoader.LoadWorkspace(workspacePath, noBackup, tfm, keepCurrentTfms); + if (msbuildWorkspace.WorkspaceItems.Length is 0) + { + Console.WriteLine("No projects converted."); + return 0; + } + foreach (var item in msbuildWorkspace.WorkspaceItems) { if (diffOnly) diff --git a/tests/TestData/SmokeTests.LegacyMSTest/Properties/AssemblyInfo.cs b/tests/TestData/SmokeTests.LegacyMSTest/Properties/AssemblyInfo.cs new file mode 100644 index 000000000..2a884ca4f --- /dev/null +++ b/tests/TestData/SmokeTests.LegacyMSTest/Properties/AssemblyInfo.cs @@ -0,0 +1,20 @@ +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +[assembly: AssemblyTitle("UnitTestProject1")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("UnitTestProject1")] +[assembly: AssemblyCopyright("Copyright © 2020")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +[assembly: ComVisible(false)] + +[assembly: Guid("a2de09a2-5c2a-4b5e-b700-9c14be5af757")] + +// [assembly: AssemblyVersion("1.0.*")] +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/tests/TestData/SmokeTests.LegacyMSTest/SmokeTests.LegacyMSTest.csproj b/tests/TestData/SmokeTests.LegacyMSTest/SmokeTests.LegacyMSTest.csproj new file mode 100644 index 000000000..14296b34e --- /dev/null +++ b/tests/TestData/SmokeTests.LegacyMSTest/SmokeTests.LegacyMSTest.csproj @@ -0,0 +1,68 @@ + + + + + + Debug + AnyCPU + {A2DE09A2-5C2A-4B5E-B700-9C14BE5AF757} + Library + Properties + UnitTestProject1 + UnitTestProject1 + v4.7.2 + 512 + {3AC096D0-A1C2-E12C-1390-A8335801FDAB};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} + 15.0 + $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) + $(ProgramFiles)\Common Files\microsoft shared\VSTT\$(VisualStudioVersion)\UITestExtensionPackages + False + UnitTest + + + + + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + + ..\packages\MSTest.TestFramework.2.1.1\lib\net45\Microsoft.VisualStudio.TestPlatform.TestFramework.dll + + + ..\packages\MSTest.TestFramework.2.1.1\lib\net45\Microsoft.VisualStudio.TestPlatform.TestFramework.Extensions.dll + + + + + + + + + + + + + + + + This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. + + + + + + \ No newline at end of file diff --git a/tests/TestData/SmokeTests.LegacyMSTest/UnitTest1.cs b/tests/TestData/SmokeTests.LegacyMSTest/UnitTest1.cs new file mode 100644 index 000000000..dbc289d34 --- /dev/null +++ b/tests/TestData/SmokeTests.LegacyMSTest/UnitTest1.cs @@ -0,0 +1,14 @@ +using System; +using Microsoft.VisualStudio.TestTools.UnitTesting; + +namespace UnitTestProject1 +{ + [TestClass] + public class UnitTest1 + { + [TestMethod] + public void TestMethod1() + { + } + } +} diff --git a/tests/TestData/SmokeTests.LegacyMSTest/packages.config b/tests/TestData/SmokeTests.LegacyMSTest/packages.config new file mode 100644 index 000000000..7079f8161 --- /dev/null +++ b/tests/TestData/SmokeTests.LegacyMSTest/packages.config @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/tests/TestData/SmokeTests.MStestCoreBaseline/Properties/AssemblyInfo.cs b/tests/TestData/SmokeTests.MStestCoreBaseline/Properties/AssemblyInfo.cs new file mode 100644 index 000000000..2a884ca4f --- /dev/null +++ b/tests/TestData/SmokeTests.MStestCoreBaseline/Properties/AssemblyInfo.cs @@ -0,0 +1,20 @@ +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +[assembly: AssemblyTitle("UnitTestProject1")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("UnitTestProject1")] +[assembly: AssemblyCopyright("Copyright © 2020")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +[assembly: ComVisible(false)] + +[assembly: Guid("a2de09a2-5c2a-4b5e-b700-9c14be5af757")] + +// [assembly: AssemblyVersion("1.0.*")] +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/tests/TestData/SmokeTests.MStestCoreBaseline/SmokeTests.MSTestCoreBaseline.csproj b/tests/TestData/SmokeTests.MStestCoreBaseline/SmokeTests.MSTestCoreBaseline.csproj new file mode 100644 index 000000000..79bdb6311 --- /dev/null +++ b/tests/TestData/SmokeTests.MStestCoreBaseline/SmokeTests.MSTestCoreBaseline.csproj @@ -0,0 +1,15 @@ + + + UnitTestProject1 + UnitTestProject1 + + + netcoreapp3.1 + false + + + + + + + \ No newline at end of file diff --git a/tests/TestData/SmokeTests.MStestCoreBaseline/UnitTest1.cs b/tests/TestData/SmokeTests.MStestCoreBaseline/UnitTest1.cs new file mode 100644 index 000000000..dbc289d34 --- /dev/null +++ b/tests/TestData/SmokeTests.MStestCoreBaseline/UnitTest1.cs @@ -0,0 +1,14 @@ +using System; +using Microsoft.VisualStudio.TestTools.UnitTesting; + +namespace UnitTestProject1 +{ + [TestClass] + public class UnitTest1 + { + [TestMethod] + public void TestMethod1() + { + } + } +} diff --git a/tests/TestData/SmokeTests.MStestCoreBaseline/packages.config b/tests/TestData/SmokeTests.MStestCoreBaseline/packages.config new file mode 100644 index 000000000..7079f8161 --- /dev/null +++ b/tests/TestData/SmokeTests.MStestCoreBaseline/packages.config @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/tests/end-to-end/Smoke.Tests/BasicConversions.cs b/tests/end-to-end/Smoke.Tests/BasicConversions.cs index 4319d94a6..880278461 100644 --- a/tests/end-to-end/Smoke.Tests/BasicConversions.cs +++ b/tests/end-to-end/Smoke.Tests/BasicConversions.cs @@ -74,6 +74,14 @@ public void ConvertsWinformsFrameworkTemplateForNet50() AssertConversionWorks(projectToConvertPath, projectBaselinePath, "net5.0-windows"); } + [Fact] + public void ConvertsLegacyMSTest() + { + var projectToConvertPath = GetCSharpProjectPath("SmokeTests.LegacyMSTest"); + var projectBaselinePath = GetCSharpProjectPath("SmokeTests.MSTestCoreBaseline"); + AssertConversionWorks(projectToConvertPath, projectBaselinePath, "netcoreapp3.1"); + } + private void AssertConversionWorks(string projectToConvertPath, string projectBaselinePath, string targetTFM) { var (baselineRootElement, convertedRootElement) = GetRootElementsForComparison(projectToConvertPath, projectBaselinePath, targetTFM);