diff --git a/MonoDevelop.MSBuild.Tests/FrameworkInfoTests.cs b/MonoDevelop.MSBuild.Tests/FrameworkInfoTests.cs new file mode 100644 index 00000000..b2e3b76a --- /dev/null +++ b/MonoDevelop.MSBuild.Tests/FrameworkInfoTests.cs @@ -0,0 +1,91 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using MonoDevelop.MSBuild.Schema; + +using NUnit.Framework; + +namespace MonoDevelop.MSBuild.Tests; + +[TestFixture] +class FrameworkInfoTests +{ + [TestCase ("net48")] + [TestCase ("net4.8")] + [TestCase ("sl3")] + [TestCase ("wp8")] + [TestCase ("uap10.0.1234")] + [TestCase ("uap10.0")] + [TestCase ("net8.0-android")] + [TestCase ("net8.0")] + public void ValidFrameworkName (string name) + { + var validationResult = FrameworkInfoProvider.Instance.ValidateFrameworkShortName (name, out _, out _, out _, out _, out _); + Assert.AreEqual (FrameworkNameValidationResult.OK, validationResult); + } + + [TestCase ("foo")] + [TestCase ("foo1.0")] + public void UnknownIdentifier (string name) + { + var validationResult = FrameworkInfoProvider.Instance.ValidateFrameworkShortName (name, out _, out _, out _, out _, out _); + Assert.AreEqual (FrameworkNameValidationResult.UnknownIdentifier, validationResult); + } + + [TestCase ("net485")] + [TestCase ("sl6.0")] + public void UnknownVersion (string name) + { + var validationResult = FrameworkInfoProvider.Instance.ValidateFrameworkShortName (name, out _, out _, out _, out _, out _); + Assert.AreEqual (FrameworkNameValidationResult.UnknownVersion, validationResult); + } + + [TestCase ("net481-client")] + [TestCase ("net35-bad")] + public void UnknownProfile (string name) + { + var validationResult = FrameworkInfoProvider.Instance.ValidateFrameworkShortName (name, out _, out _, out _, out _, out _); + Assert.AreEqual (FrameworkNameValidationResult.UnknownProfile, validationResult); + } + + [TestCase ("net6.0-fridge")] + public void UnknownPlatform (string name) + { + var validationResult = FrameworkInfoProvider.Instance.ValidateFrameworkShortName (name, out _, out _, out _, out _, out _); + Assert.AreEqual (FrameworkNameValidationResult.UnknownPlatform, validationResult); + } + + [TestCase ("net481", ".NET Framework 4.8.1")] + [TestCase ("net4.8.1", ".NET Framework 4.8.1")] + [TestCase ("wpa81", "Windows Phone (UWP) 8.1")] + [TestCase ("net6.0-android9000.0", ".NET 6.0 with platform-specific APIs for Android 9000.0")] + [TestCase ("net6.0-android", ".NET 6.0 with platform-specific APIs for Android 31.0")] + [TestCase ("net6.0-android32.0", ".NET 6.0 with platform-specific APIs for Android 32.0")] + [TestCase ("net7.0-android", ".NET 7.0 with platform-specific APIs for Android 33.0")] + [TestCase ("net6.0", ".NET 6.0")] + public void FrameworkDescription (string tfm, string description) + { + var fx = FrameworkInfoProvider.TryGetFrameworkInfo (tfm); + Assert.IsNotNull (fx); + + var actualDescription = FrameworkInfoProvider.GetDisplayDescription (fx.Reference); + Assert.AreEqual (description, actualDescription); + } + + [TestCase ("net481", ".NETFramework,Version=v4.8.1")] + [TestCase ("net4.8.1", ".NETFramework,Version=v4.8.1")] + [TestCase ("wpa81", "WindowsPhoneApp,Version=v8.1")] + [TestCase ("net6.0-android9000.0", ".NETCoreApp,Version=v6.0 | Android 9000.0")] + [TestCase ("net6.0-android", ".NETCoreApp,Version=v6.0 | Android 31.0")] + [TestCase ("net6.0-android32.0", ".NETCoreApp,Version=v6.0 | Android 32.0")] + [TestCase ("net7.0-android", ".NETCoreApp,Version=v7.0 | Android 33.0")] + [TestCase ("net6.0", ".NETCoreApp,Version=v6.0")] + public void FrameworkTitle (string tfm, string description) + { + var fx = FrameworkInfoProvider.TryGetFrameworkInfo (tfm); + Assert.IsNotNull (fx); + + var actualDescription = FrameworkInfoProvider.GetDisplayTitle (fx.Reference); + Assert.AreEqual (description, actualDescription); + } +} diff --git a/MonoDevelop.MSBuild/Language/CoreDiagnostics.cs b/MonoDevelop.MSBuild/Language/CoreDiagnostics.cs index 80e51cf4..e2a20a6f 100644 --- a/MonoDevelop.MSBuild/Language/CoreDiagnostics.cs +++ b/MonoDevelop.MSBuild/Language/CoreDiagnostics.cs @@ -547,6 +547,13 @@ class CoreDiagnostics "The target framework `{0}` has an unknown target platform `{1}`", MSBuildDiagnosticSeverity.Warning); + public const string TargetFrameworkHasUnknownProfile_Id = nameof (TargetFrameworkHasUnknownProfile); + public static readonly MSBuildDiagnosticDescriptor TargetFrameworkHasUnknownProfile = new ( + TargetFrameworkHasUnknownProfile_Id, + "Unknown framework profile", + "The target framework `{0}` has unknown profile `{1}`", + MSBuildDiagnosticSeverity.Warning); + public const string TargetFrameworkHasUnknownTargetPlatformVersion_Id = nameof (TargetFrameworkHasUnknownTargetPlatformVersion); public static readonly MSBuildDiagnosticDescriptor TargetFrameworkHasUnknownTargetPlatformVersion = new ( TargetFrameworkHasUnknownTargetPlatformVersion_Id, diff --git a/MonoDevelop.MSBuild/Language/MSBuildDocumentValidator.cs b/MonoDevelop.MSBuild/Language/MSBuildDocumentValidator.cs index cabb49f8..8075223b 100644 --- a/MonoDevelop.MSBuild/Language/MSBuildDocumentValidator.cs +++ b/MonoDevelop.MSBuild/Language/MSBuildDocumentValidator.cs @@ -735,7 +735,7 @@ void VisitPureLiteral (MSBuildElementSyntax elementSymbol, MSBuildAttributeSynta } break; case MSBuildValueKind.TargetFramework: - switch (FrameworkInfoProvider.Instance.ValidateFrameworkShortName (value, out var frameworkComponent, out var versionComponent, out var platformComponent, out var platformVersionComponent)) { + switch (FrameworkInfoProvider.Instance.ValidateFrameworkShortName (value, out var frameworkComponent, out var versionComponent, out var platformComponent, out var profileComponent, out var platformVersionComponent)) { case FrameworkNameValidationResult.OK: break; case FrameworkNameValidationResult.Malformed: @@ -750,13 +750,16 @@ void VisitPureLiteral (MSBuildElementSyntax elementSymbol, MSBuildAttributeSynta case FrameworkNameValidationResult.UnknownPlatform: AddErrorWithArgs (CoreDiagnostics.TargetFrameworkHasUnknownTargetPlatform, value, platformComponent); break; + case FrameworkNameValidationResult.UnknownProfile: + AddErrorWithArgs (CoreDiagnostics.TargetFrameworkHasUnknownProfile, value, profileComponent); + break; case FrameworkNameValidationResult.UnknownPlatformVersion: AddErrorWithArgs (CoreDiagnostics.TargetFrameworkHasUnknownTargetPlatformVersion, value, platformVersionComponent, platformComponent); break; } break; case MSBuildValueKind.TargetFrameworkIdentifier: - if (!FrameworkInfoProvider.Instance.IsFrameworkIdentifierValid (value)) { + if (!FrameworkInfoProvider.Instance.IsKnownFrameworkIdentifier (value)) { AddErrorWithArgs (CoreDiagnostics.UnknownTargetFrameworkIdentifier, value); } break; @@ -769,7 +772,7 @@ void VisitPureLiteral (MSBuildElementSyntax elementSymbol, MSBuildAttributeSynta if (Document is MSBuildRootDocument d && d.Frameworks.Count > 0) { bool foundMatch = false; foreach (var fx in d.Frameworks) { - if (FrameworkInfoProvider.AreVersionsEquivalent (fx.Version, fxv) && FrameworkInfoProvider.Instance.IsFrameworkVersionValid (fx.Framework, fxv)) { + if (FrameworkInfoProvider.AreVersionsEquivalent (fx.Version, fxv) && FrameworkInfoProvider.Instance.IsKnownFrameworkVersion (fx.Framework, fxv)) { foundMatch = true; } } diff --git a/MonoDevelop.MSBuild/Language/Typesystem/FrameworkInfo.cs b/MonoDevelop.MSBuild/Language/Typesystem/FrameworkInfo.cs index 95147c80..c4fdbc10 100644 --- a/MonoDevelop.MSBuild/Language/Typesystem/FrameworkInfo.cs +++ b/MonoDevelop.MSBuild/Language/Typesystem/FrameworkInfo.cs @@ -1,7 +1,7 @@ // Copyright (c) Microsoft. All rights reserved. // Licensed under the MIT license. See LICENSE file in the project root for full license information. -using System; +#nullable enable using NuGet.Frameworks; @@ -11,14 +11,17 @@ namespace MonoDevelop.MSBuild.Language.Typesystem // a shortname, identifier, version or profile // the "name" is the piece that's being represented and the reference is the // full ID, or as close to it as we have - class FrameworkInfo : BaseSymbol + class FrameworkInfo : BaseSymbol, IDeprecatable { - public FrameworkInfo (string name, NuGetFramework reference) + public FrameworkInfo (string name, NuGetFramework reference, string? deprecationMessage = null) : base (name, null) { Reference = reference; + DeprecationMessage = deprecationMessage; } public NuGetFramework Reference { get; } + + public string? DeprecationMessage { get; } } } diff --git a/MonoDevelop.MSBuild/Schema/DescriptionFormatter.cs b/MonoDevelop.MSBuild/Schema/DescriptionFormatter.cs index ec94c9b8..ac0023cb 100644 --- a/MonoDevelop.MSBuild/Schema/DescriptionFormatter.cs +++ b/MonoDevelop.MSBuild/Schema/DescriptionFormatter.cs @@ -58,7 +58,7 @@ string GetDesc (string id) => string.Format ( } break; case FrameworkInfo fxi: - return FrameworkInfoProvider.GetDescription (fxi.Reference); + return FrameworkInfoProvider.GetDisplayDescription (fxi.Reference); } } @@ -94,7 +94,7 @@ public static (string kind, string name) GetTitle (ISymbol info) case FileOrFolderInfo value: return (value.IsFolder? "folder" : "file", info.Name); case FrameworkInfo fxi: - return ("framework", FrameworkInfoProvider.Instance.FormatNameForTitle (fxi.Reference)); + return ("framework", FrameworkInfoProvider.GetDisplayTitle (fxi.Reference)); case TaskParameterInfo tpi: return ("parameter", tpi.Name); case FunctionInfo fi: diff --git a/MonoDevelop.MSBuild/Schema/FrameworkInfoProvider.PlatformId.cs b/MonoDevelop.MSBuild/Schema/FrameworkInfoProvider.PlatformId.cs new file mode 100644 index 00000000..c3280444 --- /dev/null +++ b/MonoDevelop.MSBuild/Schema/FrameworkInfoProvider.PlatformId.cs @@ -0,0 +1,97 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +#nullable enable + +using System; +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; + +namespace MonoDevelop.MSBuild.Schema +{ + partial class FrameworkInfoProvider + { + static class KnownPlatform + { + public const string Windows = "Windows"; + public const string Android = "Android"; + public const string iOS = "iOS"; + public const string macOS = "macOS"; + public const string tvOS = "tvOS"; + public const string MacCatalyst = "MacCatalyst"; + public const string Tizen = "Tizen"; + public const string Browser = "Browser"; + + static class LowerId + { + public const string Windows = "windows"; + public const string Android = "android"; + public const string iOS = "ios"; + public const string macOS = "macos"; + public const string tvOS = "tvos"; + public const string MacCatalyst = "maccatalyst"; + public const string Tizen = "tizen"; + public const string Browser = "browser"; + } + + public static string ToLowerCase (string platform) => platform switch { + Windows => LowerId.Windows, + Android => LowerId.Android, + iOS => LowerId.iOS, + macOS => LowerId.macOS, + tvOS => LowerId.tvOS, + MacCatalyst => LowerId.MacCatalyst, + Tizen => LowerId.Tizen, + Browser => LowerId.Browser, + _ => platform.ToLowerInvariant () + }; + + public static string ToCanonicalCase (string platform) + { + return CanonicalCaseFromLower (platform) + ?? CanonicalCaseFromLower (platform.ToLowerInvariant ()) + ?? platform; + + static string? CanonicalCaseFromLower (string platform) => platform switch { + LowerId.Windows => Windows, + LowerId.Android => Android, + LowerId.iOS => iOS, + LowerId.macOS => macOS, + LowerId.tvOS => tvOS, + LowerId.MacCatalyst => MacCatalyst, + LowerId.Tizen => Tizen, + LowerId.Browser => Browser, + _ => null + }; + } + + public static bool TryGetDefaultPlatformVersion (int netcoreappMajorVersion, string platform, [NotNullWhen (true)] out Version? defaultPlatformVersion) + { + return defaultPlatformVersions.TryGetValue ((netcoreappMajorVersion, ToLowerCase (platform)), out defaultPlatformVersion); + } + + static readonly Dictionary<(int, string), Version> defaultPlatformVersions = new () { + { (6, LowerId.Android), new Version (31, 0) }, + { (7, LowerId.Android), new Version (33, 0) }, + { (8, LowerId.Android), new Version (34, 0) }, + { (6, LowerId.iOS), new Version (15, 0) }, + { (7, LowerId.iOS), new Version (16, 1) }, + { (8, LowerId.iOS), new Version (17, 2) }, + { (5, LowerId.MacCatalyst), new Version (15, 0) }, + { (7, LowerId.MacCatalyst), new Version (16, 1) }, + { (8, LowerId.MacCatalyst), new Version (17, 2) }, + { (6, LowerId.macOS), new Version (12, 0) }, + { (7, LowerId.macOS), new Version (13, 0) }, + { (8, LowerId.macOS), new Version (14, 2) }, + { (6, LowerId.tvOS), new Version (15, 1) }, + { (7, LowerId.tvOS), new Version (16, 1) }, + { (8, LowerId.tvOS), new Version (17, 1) }, + { (7, LowerId.Tizen), new Version (7, 0) }, + { (8, LowerId.Tizen), new Version (8, 0) }, + { (6, LowerId.Windows), new Version (7, 0) }, + { (7, LowerId.Windows), new Version (7, 0) }, + { (8, LowerId.Windows), new Version (7, 0) }, + }; + } + } +} diff --git a/MonoDevelop.MSBuild/Schema/FrameworkInfoProvider.cs b/MonoDevelop.MSBuild/Schema/FrameworkInfoProvider.cs index ae71447d..47efa14f 100644 --- a/MonoDevelop.MSBuild/Schema/FrameworkInfoProvider.cs +++ b/MonoDevelop.MSBuild/Schema/FrameworkInfoProvider.cs @@ -5,13 +5,17 @@ using System; using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; using System.Linq; using System.Text; using MonoDevelop.MSBuild.Language.Typesystem; +using MonoDevelop.MSBuild.Util; using NuGet.Frameworks; +using static NuGet.Frameworks.FrameworkConstants; + namespace MonoDevelop.MSBuild.Schema { // We can't rely on checking the system or the host IDE for frameworks, as they @@ -19,8 +23,10 @@ namespace MonoDevelop.MSBuild.Schema // worldview - it often deals with ranges rather than concrete values, and // sometimes omits important things like version numbers // + // for reference, see https://learn.microsoft.com/en-us/dotnet/standard/frameworks + // // FIXME: this should really be something that schemas can extend - class FrameworkInfoProvider + partial class FrameworkInfoProvider { public static FrameworkInfoProvider Instance { get; } = new FrameworkInfoProvider (); @@ -28,17 +34,8 @@ class FrameworkInfoProvider readonly Dictionary frameworkByShortName = new(); // shortName is a value that can be used for the TargetFramework property. not all frameworks have this. - readonly record struct KnownFramework(string? ShortName, string Moniker, Version Version, string? Profile = null, string? Platform = null, Version? PlatformVersion = null); + readonly record struct KnownFramework(string? ShortName, string Identifier, Version Version, string? Profile = null, string? Platform = null, Version? PlatformVersion = null, string? deprecationMessage = null); - static class Platform - { - public const string Windows = "Windows"; - public const string Android = "Android"; - public const string iOS = "iOS"; - public const string macOS = "macOS"; - public const string tvOS = "tvOS"; - public const string MacCatalyst = "MacCatalyst"; - } static class FxID { @@ -57,10 +54,12 @@ static class FxID public FrameworkInfoProvider () { Version CreateVersion (int versionMajor, int versionMinor, int versionBuild) => versionBuild > -1 ? new Version (versionMajor, versionMinor, versionBuild) : new Version (versionMajor, versionMinor); - void AddLegacy (string? shortName, string moniker, int versionMajor, int versionMinor, int versionBuild = -1, string? profile = null) => frameworks.Add (new KnownFramework (shortName, moniker, CreateVersion (versionMajor, versionMinor, versionBuild), profile)); + void AddLegacy (string? shortName, string identifier, int versionMajor, int versionMinor, int versionBuild = -1, string? profile = null, string? deprecationMessage = null) + => frameworks.Add (new KnownFramework (shortName, identifier, CreateVersion (versionMajor, versionMinor, versionBuild), profile, deprecationMessage)); void AddNetFx (string shortName, int versionMajor, int versionMinor, int versionBuild = -1, string? profile = null) => AddLegacy (shortName, FxID.NETFramework, versionMajor, versionMinor, versionBuild, profile); + AddNetFx ("net11", 1, 1); AddNetFx ("net20", 2, 0); AddNetFx ("net30", 3, 0); AddNetFx ("net35", 3, 5); @@ -161,36 +160,41 @@ public FrameworkInfoProvider () AddNetCore (5, 0, "net5.0-windows"); AddNetCore (6, 0, "net6.0"); - AddNetCore (6, 0, "net6.0-windows", Platform.Windows); - AddNetCore (6, 0, "net6.0-android", Platform.Android); - AddNetCore (6, 0, "net6.0-ios", Platform.iOS); - AddNetCore (6, 0, "net6.0-maccatalyst", Platform.MacCatalyst); - AddNetCore (6, 0, "net6.0-macos", Platform.macOS); - AddNetCore (6, 0, "net6.0-tvos", Platform.tvOS); + AddNetCore (6, 0, "net6.0-windows", KnownPlatform.Windows); + AddNetCore (6, 0, "net6.0-android", KnownPlatform.Android); + AddNetCore (6, 0, "net6.0-ios", KnownPlatform.iOS); + AddNetCore (6, 0, "net6.0-maccatalyst", KnownPlatform.MacCatalyst); + AddNetCore (6, 0, "net6.0-macos", KnownPlatform.macOS); + AddNetCore (6, 0, "net6.0-tvos", KnownPlatform.tvOS); AddNetCore (7, 0, "net7.0"); - AddNetCore (7, 0, "net7.0-windows", Platform.Windows); - AddNetCore (7, 0, "net7.0-android", Platform.Android); - AddNetCore (7, 0, "net7.0-ios", Platform.iOS); - AddNetCore (7, 0, "net7.0-maccatalyst", Platform.MacCatalyst); - AddNetCore (7, 0, "net7.0-macos", Platform.macOS); - AddNetCore (7, 0, "net7.0-tvos", Platform.tvOS); + AddNetCore (7, 0, "net7.0-windows", KnownPlatform.Windows); + AddNetCore (7, 0, "net7.0-android", KnownPlatform.Android); + AddNetCore (7, 0, "net7.0-ios", KnownPlatform.iOS); + AddNetCore (7, 0, "net7.0-maccatalyst", KnownPlatform.MacCatalyst); + AddNetCore (7, 0, "net7.0-macos", KnownPlatform.macOS); + AddNetCore (7, 0, "net7.0-tvos", KnownPlatform.tvOS); + AddNetCore (7, 0, "net7.0-tizen", KnownPlatform.Tizen); AddNetCore (8, 0, "net8.0"); - AddNetCore (8, 0, "net8.0-windows", Platform.Windows); - AddNetCore (8, 0, "net8.0-android", Platform.Android); - AddNetCore (8, 0, "net8.0-ios", Platform.iOS); - AddNetCore (8, 0, "net8.0-maccatalyst", Platform.MacCatalyst); - AddNetCore (8, 0, "net8.0-macos", Platform.macOS); - AddNetCore (8, 0, "net8.0-tvos", Platform.tvOS); + AddNetCore (8, 0, "net8.0-windows", KnownPlatform.Windows); + AddNetCore (8, 0, "net8.0-android", KnownPlatform.Android); + AddNetCore (8, 0, "net8.0-ios", KnownPlatform.iOS); + AddNetCore (8, 0, "net8.0-maccatalyst", KnownPlatform.MacCatalyst); + AddNetCore (8, 0, "net8.0-macos", KnownPlatform.macOS); + AddNetCore (8, 0, "net8.0-tvos", KnownPlatform.tvOS); + AddNetCore (7, 0, "net8.0-tizen", KnownPlatform.Tizen); + AddNetCore (7, 0, "net8.0-browser", KnownPlatform.Browser); AddNetCore (9, 0, "net9.0"); - AddNetCore (9, 0, "net9.0-windows", Platform.Windows); - AddNetCore (9, 0, "net9.0-android", Platform.Android); - AddNetCore (9, 0, "net9.0-ios", Platform.iOS); - AddNetCore (9, 0, "net9.0-maccatalyst", Platform.MacCatalyst); - AddNetCore (9, 0, "net9.0-macos", Platform.macOS); - AddNetCore (9, 0, "net9.0-tvos", Platform.tvOS); + AddNetCore (9, 0, "net9.0-windows", KnownPlatform.Windows); + AddNetCore (9, 0, "net9.0-android", KnownPlatform.Android); + AddNetCore (9, 0, "net9.0-ios", KnownPlatform.iOS); + AddNetCore (9, 0, "net9.0-maccatalyst", KnownPlatform.MacCatalyst); + AddNetCore (9, 0, "net9.0-macos", KnownPlatform.macOS); + AddNetCore (9, 0, "net9.0-tvos", KnownPlatform.tvOS); + AddNetCore (7, 0, "net9.0-tizen", KnownPlatform.Tizen); + AddNetCore (7, 0, "net9.0-browser", KnownPlatform.Browser); AddLegacy (null, FxID.MonoAndroid, 1, 0); AddLegacy (null, FxID.MonoAndroid, 2, 3); @@ -213,9 +217,54 @@ public FrameworkInfoProvider () AddLegacy (null, FxID.XamarinIOS, 1, 0); AddLegacy (null, FxID.MonoUE, 1, 0); + AddLegacy ("sl2", FrameworkIdentifiers.Silverlight, 2, 0); + AddLegacy ("sl3", FrameworkIdentifiers.Silverlight, 3, 0); + AddLegacy ("sl4", FrameworkIdentifiers.Silverlight, 4, 0); + AddLegacy ("sl5", FrameworkIdentifiers.Silverlight, 5, 0); + + AddLegacy ("wp", FrameworkIdentifiers.WindowsPhone, 7, 0); + AddLegacy ("wp7", FrameworkIdentifiers.WindowsPhone, 7, 0); + AddLegacy ("wp75", FrameworkIdentifiers.WindowsPhone, 7, 5); + AddLegacy ("wp8", FrameworkIdentifiers.WindowsPhone, 8, 0); + AddLegacy ("wp81", FrameworkIdentifiers.WindowsPhone, 8, 1); + AddLegacy ("wpa", FrameworkIdentifiers.WindowsPhoneApp, 8, 1); + AddLegacy ("wpa81", FrameworkIdentifiers.WindowsPhoneApp, 8, 1); + + AddLegacy ("win", FrameworkIdentifiers.Windows, 8, 0, deprecationMessage: "Use `netcore45`"); + AddLegacy ("win8", FrameworkIdentifiers.Windows, 8, 0, deprecationMessage: "Use `netcore45`"); // equivalent to netcore45 + AddLegacy ("win81", FrameworkIdentifiers.Windows, 8, 1, deprecationMessage: "Use `netcore451`"); // equivalent to netcore451 + AddLegacy ("win10", FrameworkIdentifiers.Windows, 10, 0, deprecationMessage: "Use `uap10.0`"); // equivalent to uap10.0 + + AddLegacy ("winrt", FrameworkIdentifiers.WinRT, 4, 5, deprecationMessage: "Use `netcore45`"); + AddLegacy ("winrt45", FrameworkIdentifiers.WinRT, 4, 5, deprecationMessage: "Use `netcore45`"); // equivalent to netcore45 + + AddLegacy ("netcore", FrameworkIdentifiers.NetCore, 4, 5); + AddLegacy ("netcore45", FrameworkIdentifiers.NetCore, 4, 5); + AddLegacy ("netcore451", FrameworkIdentifiers.NetCore, 4, 5, 1); + AddLegacy ("netcore50", FrameworkIdentifiers.NetCore, 5, 0, deprecationMessage: "Use `uap10.0`"); // equivalent to uap10.0 + + AddLegacy ("uap", FrameworkIdentifiers.UAP, 10, 0); + AddLegacy ("uap10.0", FrameworkIdentifiers.UAP, 10, 0); + + AddLegacy ("tizen", FrameworkIdentifiers.Tizen, 3, 0); + AddLegacy ("tizen30", FrameworkIdentifiers.Tizen, 3, 0); + AddLegacy ("tizen40", FrameworkIdentifiers.Tizen, 4, 0); + AddLegacy ("tizen50", FrameworkIdentifiers.Tizen, 5, 0); + AddLegacy ("tizen60", FrameworkIdentifiers.Tizen, 6, 0); + + AddLegacy ("netnano", FrameworkIdentifiers.NanoFramework, 1, 0); + + AddLegacy ("netmf20", FrameworkIdentifiers.NetMicro, 2, 0); + AddLegacy ("netmf30", FrameworkIdentifiers.NetMicro, 3, 0); + AddLegacy ("netmf35", FrameworkIdentifiers.NetMicro, 3, 5); + AddLegacy ("netmf41", FrameworkIdentifiers.NetMicro, 4, 1); + AddLegacy ("netmf42", FrameworkIdentifiers.NetMicro, 4, 2); + AddLegacy ("netmf43", FrameworkIdentifiers.NetMicro, 4, 3); + AddLegacy ("netmf44", FrameworkIdentifiers.NetMicro, 4, 4); + // sort to make other operations more efficient frameworks.Sort ((x, y) => { - int cmp = string.Compare (x.Moniker, y.Moniker, StringComparison.Ordinal); + int cmp = string.Compare (x.Identifier, y.Identifier, StringComparison.Ordinal); if (cmp != 0) { return cmp; } @@ -247,9 +296,9 @@ public FrameworkInfoProvider () } } - public FrameworkNameValidationResult ValidateFrameworkShortName (string shortName, out string? frameworkComponent, out Version? versionComponent, out string? platformComponent, out Version? platformVersionComponent) + public FrameworkNameValidationResult ValidateFrameworkShortName (string shortName, out string? frameworkComponent, out Version? versionComponent, out string? platformComponent, out string? profileComponent, out Version? platformVersionComponent) { - frameworkComponent = platformComponent = null; + frameworkComponent = platformComponent = profileComponent = null; versionComponent = platformVersionComponent = null; if (frameworkByShortName.ContainsKey (shortName)) { @@ -265,6 +314,7 @@ public FrameworkNameValidationResult ValidateFrameworkShortName (string shortNam frameworkComponent = framework.Framework; versionComponent = framework.Version; platformComponent = framework.Platform; + profileComponent = framework.Profile; platformVersionComponent = framework.PlatformVersion; if (framework.IsUnsupported) { @@ -279,25 +329,37 @@ public FrameworkNameValidationResult ValidateFrameworkShortName (string shortNam bool foundIdentifier = false; bool foundVersion = false; bool foundPlatform = false; - foreach (var fx in GetFrameworksWithMoniker (framework.Framework)) { + bool foundProfile = false; + + foreach (var fx in GetFrameworksWithIdentifier (framework.Framework)) { foundIdentifier = true; if (AreVersionsEquivalent (framework.Version, fx.Version)) { foundVersion = true; - if (string.Equals (framework.Platform, fx.Platform, StringComparison.OrdinalIgnoreCase)) { + if (framework.HasPlatform && ArePlatformsEquivalent (framework.Platform, fx.Platform)) { foundPlatform = true; } + if (framework.HasProfile & ArePlatformsEquivalent (framework.Profile, fx.Profile)) { + foundProfile = true; + } } } if (!foundIdentifier) { return FrameworkNameValidationResult.UnknownIdentifier; } if (!foundVersion) { + // for UAP we can't validate the revisions as we don't have the data, so just ignore it + if (framework.Framework == "UAP" && framework.Version.Major == 10 && framework.Version.Minor == 0) { + return FrameworkNameValidationResult.OK; + } return FrameworkNameValidationResult.UnknownVersion; } - if (!foundPlatform) { + if (framework.HasPlatform && !foundPlatform) { return FrameworkNameValidationResult.UnknownPlatform; } + if (framework.HasProfile && !foundProfile) { + return FrameworkNameValidationResult.UnknownProfile; + } // TODO: unknown platform version return FrameworkNameValidationResult.OK; } @@ -326,49 +388,49 @@ static bool TryParseShortName(string shortName) static bool IsValidProfileChar (char c) => (c >= 48 && c <= 57) || (c >= 65 && c <= 90) || (c >= 97 && c <= 122) || c == 46 || c == 43 || c == 45; */ - public bool IsFrameworkIdentifierValid (string moniker) => GetFrameworksWithMoniker (moniker).Any (); + public bool IsKnownFrameworkIdentifier (string identifier) => GetFrameworksWithIdentifier (identifier).Any (); - public bool IsFrameworkVersionValid (string moniker, Version version) => GetFrameworksWithMonikerAndVersion (moniker, version).Any (); + public bool IsKnownFrameworkVersion (string identifier, Version version) => GetFrameworksWithIdentifierAndVersion (identifier, version).Any (); - IEnumerable GetFrameworksWithMoniker (string moniker) + IEnumerable GetFrameworksWithIdentifier (string identifier) { // take advantage of the sorting to limit the enumeration - bool foundMoniker = false; + bool foundIdentifier = false; foreach (var fx in frameworks) { - if (string.Equals (fx.Moniker, moniker, StringComparison.OrdinalIgnoreCase)) { - foundMoniker = true; + if (string.Equals (fx.Identifier, identifier, StringComparison.OrdinalIgnoreCase)) { + foundIdentifier = true; yield return fx; } - else if (foundMoniker) { + else if (foundIdentifier) { yield break; } } } - IEnumerable GetFrameworksWithMonikerAndVersion (string moniker, Version version) + IEnumerable GetFrameworksWithIdentifierAndVersion (string identifier, Version version) { // take advantage of the sorting to limit the enumeration - bool foundMoniker = false; + bool foundIdentifier = false; bool foundVersion = false; foreach (var fx in frameworks) { - if (string.Equals (fx.Moniker, moniker, StringComparison.OrdinalIgnoreCase)) { - foundMoniker = true; + if (string.Equals (fx.Identifier, identifier, StringComparison.OrdinalIgnoreCase)) { + foundIdentifier = true; if (AreVersionsEquivalent (version, fx.Version)) { foundVersion = true; yield return fx; } else if (foundVersion) { yield break; } - } else if (foundMoniker) { + } else if (foundIdentifier) { yield break; } } } - static NuGetFramework ToNugetFramework (KnownFramework fx) => new (fx.Moniker, fx.Version, fx.Profile ?? "", fx.Platform ?? "", fx.PlatformVersion ?? FrameworkConstants.EmptyVersion); + static NuGetFramework ToNugetFramework (KnownFramework fx) => new (fx.Identifier, fx.Version, fx.Profile ?? "", fx.Platform ?? "", fx.PlatformVersion ?? FrameworkConstants.EmptyVersion); - public bool IsFrameworkProfileValid (string moniker, Version version, string profile) + public bool IsFrameworkProfileValid (string identifier, Version version, string profile) { - foreach (var fx in GetFrameworksWithMonikerAndVersion (moniker, version)) { + foreach (var fx in GetFrameworksWithIdentifierAndVersion (identifier, version)) { if (string.Equals (fx.Profile, profile, StringComparison.OrdinalIgnoreCase)) { return true; } @@ -376,11 +438,20 @@ public bool IsFrameworkProfileValid (string moniker, Version version, string pro return false; } + public static FrameworkInfo? TryGetFrameworkInfo (string shortName) + { + var fullref = NuGetFramework.ParseFolder (shortName); + if (fullref.IsSpecificFramework) { + return new FrameworkInfo (shortName, fullref); + } + return null; + } + public IEnumerable GetFrameworksWithShortNames () { foreach (var fx in frameworks) { if (fx.ShortName != null) { - yield return new FrameworkInfo (fx.ShortName, ToNugetFramework (fx)); + yield return new FrameworkInfo (fx.ShortName, ToNugetFramework (fx), fx.deprecationMessage); } } } @@ -390,37 +461,37 @@ public IEnumerable GetFrameworkIdentifiers () // as they are sorted we can deduplicate by skipping values that are the same as the last returned value string? lastId = null; foreach (var fx in frameworks) { - if (string.Equals (fx.Moniker, lastId, StringComparison.Ordinal)) { + if (string.Equals (fx.Identifier, lastId, StringComparison.Ordinal)) { continue; } - lastId = fx.Moniker; - yield return new FrameworkInfo (fx.Moniker, new NuGetFramework (lastId)); + lastId = fx.Identifier; + yield return new FrameworkInfo (fx.Identifier, new NuGetFramework (lastId), fx.deprecationMessage); } } public IEnumerable GetFrameworkVersions (string identifier) { Version? lastReturned = null; - foreach (var fx in GetFrameworksWithMoniker (identifier)) { + foreach (var fx in GetFrameworksWithIdentifier (identifier)) { if (lastReturned is not null && AreVersionsEquivalent (fx.Version, lastReturned)) { continue; } lastReturned = fx.Version; - yield return new FrameworkInfo ("v" + FormatDisplayVersion (fx.Version), new NuGetFramework (fx.Moniker, fx.Version)); + yield return new FrameworkInfo ("v" + FormatDisplayVersion (fx.Version), new NuGetFramework (fx.Identifier, fx.Version), fx.deprecationMessage); } } - public IEnumerable GetFrameworkProfiles (string moniker, Version version) + public IEnumerable GetFrameworkProfiles (string identifier, Version version) { foreach (var fx in frameworks) { - if (!string.Equals (fx.Moniker, moniker, StringComparison.OrdinalIgnoreCase)) { + if (!string.Equals (fx.Identifier, identifier, StringComparison.OrdinalIgnoreCase)) { continue; } if (!AreVersionsEquivalent (version, fx.Version)) { continue; } if (fx.Profile is string profile) { - yield return new FrameworkInfo (profile, new NuGetFramework (fx.Moniker, fx.Version, fx.Profile)); + yield return new FrameworkInfo (profile, new NuGetFramework (fx.Identifier, fx.Version, fx.Profile), fx.deprecationMessage); } } } @@ -440,18 +511,26 @@ public static bool AreVersionsEquivalent (Version v1, Version v2) return true; } + public static bool ArePlatformsEquivalent (string? p1, string? p2) + { + if (string.IsNullOrEmpty (p1)) { + return string.IsNullOrEmpty (p2); + } + return string.Equals (p1, p2, StringComparison.OrdinalIgnoreCase); + } + public static string FormatDisplayVersion (Version version) { if (version.Build > 0) { - return $"{version.Major}.{version.Minor}.{version.Revision}.{version.Build}"; + return $"{version.Major}.{version.Minor}.{version.Build}"; } if (version.Revision > 0) { - return $"{version.Major}.{version.Minor}.{version.Revision}"; + return $"{version.Major}.{version.Minor}.{version.Build}.{version.Revision}"; } return $"{version.Major}.{version.Minor}"; } - public static string? GetDescription (NuGetFramework fx) + public static string? GetDisplayDescription (NuGetFramework fx) { switch (fx.Framework.ToLowerInvariant ()) { case ".netframework": @@ -462,7 +541,13 @@ public static string FormatDisplayVersion (Version version) if (fx.Version.Major <= 5) { return WithVersion (".NET Core"); } - return WithVersionAndPlatform (".NET"); + if (string.IsNullOrEmpty (fx.Platform)) { + return $".NET {FormatDisplayVersion (fx.Version)}"; + } + if (TryGetPlatformVersionForDisplay (fx, out var platformDisplayVersion)) { + return $".NET {FormatDisplayVersion (fx.Version)} with platform-specific APIs for {FormatPlatformNameForTitle (fx)} {platformDisplayVersion}"; + } + return $".NET {FormatDisplayVersion (fx.Version)} with platform-specific APIs for {FormatPlatformNameForTitle (fx)}"; case ".netportable": if (string.IsNullOrEmpty (fx.Profile)) { return "Portable Class Library"; @@ -481,6 +566,29 @@ public static string FormatDisplayVersion (Version version) return "Xamarin.watchOS"; case "monoue": return "Mono for Unreal Engine"; + case ".netcore": + return (fx.Version.Major, fx.Version.Minor, fx.Version.Build) switch { + (4, 5, 0) => "Windows Store 8.0", + (4, 5, 1) => "Windows Store 8.1", + (5, 0, 0) => "Universal Windows Platform 10.0", + _ => "Windows Store" + }; + case "windows": + return WithVersion ("Windows Store"); + case "windowsphone": + return WithVersion ("Windows Phone"); + case "windowsphoneapp": + return WithVersion ("Windows Phone (UWP)"); + case "uap": + return WithVersion ("Universal Windows Platform"); + case "silverlight": + return WithVersion ("Silverlight"); + case "tizen": + return WithVersion ("Tizen"); + case ".netnanoframework": + return ".NET Nano Framework"; + case ".netmicroframework": + return ".NET Micro Framework"; } return null; @@ -491,23 +599,9 @@ string WithVersion (string description) } return $"{description} {FormatDisplayVersion (fx.Version)}"; } - - string WithVersionAndPlatform (string description) - { - if (fx.Version.Major == 0) { - return description; - } - if (string.IsNullOrEmpty (fx.Platform)) { - return $"{description} {FormatDisplayVersion (fx.Version)}"; - } - if (fx.PlatformVersion.Major == 0) { - return $"{description} {FormatDisplayVersion (fx.Version)} framework with platform-specific APIs for {FormatPlatformNameForTitle (fx)}"; - } - return $"{description} {FormatDisplayVersion (fx.Version)} framework with platform-specific APIs for {FormatPlatformNameForTitle (fx)} {FormatDisplayVersion (fx.PlatformVersion)}"; - } } - public string FormatNameForTitle (NuGetFramework reference) + public static string GetDisplayTitle (NuGetFramework reference) { var titleName = reference.DotNetFrameworkName; if (!reference.HasPlatform) { @@ -519,22 +613,30 @@ public string FormatNameForTitle (NuGetFramework reference) sb.Append (" | "); sb.Append (platformName); - sb.Append (" "); - AppendDisplayVersion (sb, reference.PlatformVersion); + if (TryGetPlatformVersionForDisplay (reference, out var platformDisplayVersion)) { + sb.Append (" "); + sb.Append (platformDisplayVersion); + } return sb.ToString (); } - static string FormatPlatformNameForTitle (NuGetFramework reference) => reference.Platform.ToLowerInvariant () switch { - "windows" => Platform.Windows, - "android" => Platform.Android, - "ios" => Platform.iOS, - "macos" => Platform.macOS, - "tvos" => Platform.tvOS, - "maccatalyst" => Platform.MacCatalyst, - _ => reference.Platform - }; + static string FormatPlatformNameForTitle (NuGetFramework reference) => KnownPlatform.ToCanonicalCase (reference.Platform); + + static bool TryGetPlatformVersionForDisplay (NuGetFramework fx, [NotNullWhen (true)] out string? displayVersion) + { + if (fx.IsNet5Era && !string.IsNullOrEmpty (fx.Platform)) { + var platformVersion = fx.PlatformVersion; + if (platformVersion?.Major > 0 || KnownPlatform.TryGetDefaultPlatformVersion (fx.Version.Major, fx.Platform, out platformVersion)) { + displayVersion = FormatDisplayVersion (platformVersion); + return true; + } + } + + displayVersion = null; + return false; + } static void AppendDisplayVersion (StringBuilder sb, Version version) { @@ -563,6 +665,7 @@ internal enum FrameworkNameValidationResult UnknownIdentifier, UnknownVersion, UnknownPlatform, + UnknownProfile, UnknownPlatformVersion } } diff --git a/MonoDevelop.MSBuild/Schema/MSBuildCompletionExtensions.cs b/MonoDevelop.MSBuild/Schema/MSBuildCompletionExtensions.cs index 67c87919..96e24ab3 100644 --- a/MonoDevelop.MSBuild/Schema/MSBuildCompletionExtensions.cs +++ b/MonoDevelop.MSBuild/Schema/MSBuildCompletionExtensions.cs @@ -394,7 +394,7 @@ public static ISymbol GetResolvedReference (this MSBuildResolveResult rr, MSBuil case MSBuildReferenceKind.KnownValue: return rr.GetKnownValueReference (); case MSBuildReferenceKind.TargetFramework: - return ResolveFramework (rr.GetTargetFrameworkReference ()); + return FrameworkInfoProvider.TryGetFrameworkInfo (rr.GetTargetFrameworkReference ()); case MSBuildReferenceKind.TargetFrameworkIdentifier: return BestGuessResolveFrameworkIdentifier (rr.GetTargetFrameworkIdentifierReference (), doc.Frameworks); case MSBuildReferenceKind.TargetFrameworkVersion: @@ -431,15 +431,6 @@ public static ISymbol GetResolvedReference (this MSBuildResolveResult rr, MSBuil return null; } - static FrameworkInfo ResolveFramework (string shortname) - { - var fullref = NuGetFramework.ParseFolder (shortname); - if (fullref.IsSpecificFramework) { - return new FrameworkInfo (shortname, fullref); - } - return null; - } - static FrameworkInfo BestGuessResolveFrameworkIdentifier (string identifier, IReadOnlyList docTfms) { //if any tfm in the doc matches, assume it's referring to that