diff --git a/Make.versions b/Make.versions index 268562e3f22d..94658084879f 100644 --- a/Make.versions +++ b/Make.versions @@ -100,5 +100,46 @@ MACCATALYST_NUGET_OS_VERSION=17.2 # So we've made the decision that the default target platform version is # always the latest target platform version. -# .NET 7 versions are bumped using maestro. +# +# Here we list all the releases we support for each platform. +# +# Format: space-separated list of TargetFramework-OSVersion +# +# Example: +# +# SUPPORTED_API_VERSIONS_IOS=net8.0-17.0 net8.0-17.2 +# +# This means the iOS workload shipped from the current branch supports projects with: +# net8.0-17.0 +# and +# net8.0-17.2 +# and even: +# net8.0-17.0;net8.0-17.2 +# +# When shipping support for a preview Xcode, we might add entries here for a preview release into a stable release. +# +# Example: +# +# SUPPORTED_API_VERSIONS_IOS=net9.0-18.0 +# +# If the current branch is stable .NET 8 using Xcode 15.0 (aka iOS 17.0), this +# would add support for trying the preview release by doing: +# +# net9.0-18.0 +# true +# +# Note that any SUPPORTED_API_VERSIONS entry below for older OS versions need a corresponding entry in +# the eng/Version.Details.xml and eng/Versions.props files. +# + +# First add the versions for the current branch. DO NOT TOUCH THIS. Add older branches below. + +SUPPORTED_API_VERSIONS_IOS=$(DOTNET_TFM)-$(IOS_NUGET_OS_VERSION) +SUPPORTED_API_VERSIONS_TVOS=$(DOTNET_TFM)-$(TVOS_NUGET_OS_VERSION) +SUPPORTED_API_VERSIONS_MACOS=$(DOTNET_TFM)-$(MACOS_NUGET_OS_VERSION) +SUPPORTED_API_VERSIONS_MACCATALYST=$(DOTNET_TFM)-$(MACCATALYST_NUGET_OS_VERSION) + +# Add older versions here! + +# (work on adding older versions is in progress) diff --git a/dotnet/Makefile b/dotnet/Makefile index db439eea13e0..c9129c9319bf 100644 --- a/dotnet/Makefile +++ b/dotnet/Makefile @@ -163,10 +163,10 @@ $(foreach platform,$(DOTNET_PLATFORMS),$(eval $(call ImplicitNamespaceImports,$( define SupportedTargetPlatforms Microsoft.$(1).Sdk/targets/Microsoft.$(1).Sdk.SupportedTargetPlatforms.props: $(TOP)/builds/Versions-$(1).plist.in Makefile ./generate-target-platforms.csharp Makefile $(Q) rm -f $$@.tmp - $(Q) ./generate-target-platforms.csharp $(1) $$@.tmp + $(Q) ./generate-target-platforms.csharp $(1) "$(DOTNET_TFM)" "$$(SUPPORTED_API_VERSIONS_$(2))" $$@.tmp $(Q) mv $$@.tmp $$@ endef -$(foreach platform,$(DOTNET_PLATFORMS),$(eval $(call SupportedTargetPlatforms,$(platform)))) +$(foreach platform,$(DOTNET_PLATFORMS),$(eval $(call SupportedTargetPlatforms,$(platform),$(shell echo $(platform) | tr a-z A-Z)))) define WorkloadTargets Workloads/Microsoft.NET.Sdk.$(1)/WorkloadManifest.json: Makefile $(TOP)/Make.config.inc $(GIT_DIRECTORY)/HEAD $(GIT_DIRECTORY)/index Makefile generate-workloadmanifest-json.csharp | Workloads/Microsoft.NET.Sdk.$(1) diff --git a/dotnet/generate-target-platforms.csharp b/dotnet/generate-target-platforms.csharp index 469a03e4d081..b7a2ed4ad18e 100755 --- a/dotnet/generate-target-platforms.csharp +++ b/dotnet/generate-target-platforms.csharp @@ -6,7 +6,7 @@ using System.IO; using System.Xml; var args = Args; -var expectedArgumentCount = 2; +var expectedArgumentCount = 4; if (args.Length != expectedArgumentCount) { Console.WriteLine ($"Need {expectedArgumentCount} arguments, got {args.Length}"); Environment.Exit (1); @@ -15,18 +15,22 @@ if (args.Length != expectedArgumentCount) { var idx = 0; var platform = args [idx++]; +var dotnetTfm = args [idx++]; +var supportedApiVersions = args [idx++].Split (' ').Select (v => v.Replace (dotnetTfm + "-", "")).ToArray (); var outputPath = args [idx++]; var plistPath = $"../builds/Versions-{platform}.plist.in" var doc = new XmlDocument (); doc.Load (plistPath); -var knownVersions = doc.SelectNodes ($"/plist/dict/key[text()='KnownVersions']/following-sibling::dict[1]/key[text()='{platform}']/following-sibling::array[1]/string").Cast ().Select (v => v.InnerText).ToArray (); var supportedTargetPlatformVersions = doc.SelectNodes ($"/plist/dict/key[text()='SupportedTargetPlatformVersions']/following-sibling::dict[1]/key[text()='{platform}']/following-sibling::array[1]/string").Cast ().Select (v => v.InnerText).ToArray (); +var currentSupportedTPVs = supportedTargetPlatformVersions.Where (v => v.StartsWith (dotnetTfm + "-", StringComparison.Ordinal)).Select (v => v.Substring (dotnetTfm.Length + 1)); var minSdkVersionName = $"DOTNET_MIN_{platform.ToUpper ()}_SDK_VERSION"; var minSdkVersionString = File.ReadAllLines ("../Make.config").Single (v => v.StartsWith (minSdkVersionName + "=", StringComparison.Ordinal)).Substring (minSdkVersionName.Length + 1); var minSdkVersion = Version.Parse (minSdkVersionString); +Console.WriteLine (string.Join (";", supportedApiVersions)); + using (TextWriter writer = new StreamWriter (outputPath)) { writer.WriteLine ($""); writer.WriteLine ($""); @@ -35,7 +39,7 @@ using (TextWriter writer = new StreamWriter (outputPath)) { foreach (var version in supportedTargetPlatformVersions) { writer.Write ($"\t\t<{platform}SdkSupportedTargetPlatformVersion Include=\"{version}\" "); - if (!knownVersions.Contains (version)) + if (!supportedApiVersions.Contains (version)) writer.Write ($"DefineConstantsOnly=\"true\" "); writer.WriteLine ("/>"); } diff --git a/dotnet/targets/Xamarin.Shared.Sdk.targets b/dotnet/targets/Xamarin.Shared.Sdk.targets index d975f306e11c..490aaa9d0ac3 100644 --- a/dotnet/targets/Xamarin.Shared.Sdk.targets +++ b/dotnet/targets/Xamarin.Shared.Sdk.targets @@ -2096,6 +2096,25 @@ + + + + <_XamarinApplicableTargetPlatformVersion Include="@(SdkSupportedTargetPlatformVersion)" Condition="'@(SdkSupportedTargetPlatformVersion)' != '' and '%(SdkSupportedTargetPlatformVersion.DefineConstantsOnly)' != 'true'" RemoveMetadata="DefineConstantsOnly" /> + <_XamarinValidTargetPlatformVersion Include="@(_XamarinApplicableTargetPlatformVersion)" Condition="'@(_XamarinApplicableTargetPlatformVersion)' != '' and $([MSBuild]::VersionEquals(%(Identity), $(TargetPlatformVersion)))" /> + + + + <_XamarinTargetPlatformVersionSupported Condition="'$(_XamarinTargetPlatformVersionSupported)' == '' and '@(_XamarinValidTargetPlatformVersion)' != ''" >true + <_XamarinValidTargetPlatformVersions Condition="'@(_XamarinApplicableTargetPlatformVersion)' != ''" >@(_XamarinApplicableTargetPlatformVersion, '%0a') + <_XamarinValidTargetPlatformVersions Condition="'@(_XamarinApplicableTargetPlatformVersion)' == ''" >None + + + + + diff --git a/tests/dotnet/UnitTests/ProjectTest.cs b/tests/dotnet/UnitTests/ProjectTest.cs index d1a66472c19c..9d34ea553b77 100644 --- a/tests/dotnet/UnitTests/ProjectTest.cs +++ b/tests/dotnet/UnitTests/ProjectTest.cs @@ -1,5 +1,6 @@ using System.Runtime.InteropServices; using System.Diagnostics; +using System.Xml; using Mono.Cecil; @@ -1541,15 +1542,13 @@ internal static IList RemovePostCurrentOnMacCatalyst (IList self [TestCase (ApplePlatform.iOS)] [TestCase (ApplePlatform.TVOS)] [TestCase (ApplePlatform.MacOSX)] - [Ignore ("Multi-targeting support has been temporarily reverted/postponed")] public void InvalidTargetPlatformVersion (ApplePlatform platform) { Configuration.IgnoreIfIgnoredPlatform (platform); // Pick a target platform version we don't support (such as iOS 6.66). var targetFrameworks = Configuration.DotNetTfm + "-" + platform.AsString ().ToLowerInvariant () + "6.66"; - var supportedApiVersion = Configuration.GetVariableArray ($"SUPPORTED_API_VERSIONS_{platform.AsString ().ToUpperInvariant ()}"); - var validTargetPlatformVersions = supportedApiVersion.Where (v => v.StartsWith (Configuration.DotNetTfm + "-", StringComparison.Ordinal)).Select (v => v.Substring (Configuration.DotNetTfm.Length + 1)); + var supportedApiVersions = GetSupportedApiVersions (platform); var project = "MultiTargetingLibrary"; var project_path = GetProjectPath (project, platform: platform); @@ -1558,8 +1557,71 @@ public void InvalidTargetPlatformVersion (ApplePlatform platform) properties ["cmdline:AllTheTargetFrameworks"] = targetFrameworks; var rv = DotNet.AssertBuildFailure (project_path, properties); var errors = BinLog.GetBuildLogErrors (rv.BinLogPath).ToArray (); - Assert.AreEqual (1, errors.Length, "Error count"); - Assert.AreEqual ($"6.66 is not a valid TargetPlatformVersion for {platform.AsString ()}. Valid versions include:\n{string.Join ('\n', validTargetPlatformVersions)}", errors [0].Message, "Error message"); + AssertErrorMessages (errors, $"6.66 is not a valid TargetPlatformVersion for {platform.AsString ()}. Valid versions include:\n{string.Join ('\n', supportedApiVersions)}"); + } + + [Test] + [TestCase (ApplePlatform.MacCatalyst)] + [TestCase (ApplePlatform.iOS)] + [TestCase (ApplePlatform.TVOS)] + [TestCase (ApplePlatform.MacOSX)] + public void UnsupportedTargetPlatformVersion (ApplePlatform platform) + { + Configuration.IgnoreIfIgnoredPlatform (platform); + + // Pick a target platform version that we don't really support, + // but don't show an error in .NET 8 because of backwards compat. + // The earliest target OS version should do. + var minSupportedOSVersion = GetSupportedTargetPlatformVersions (platform).First (); + var targetFrameworks = Configuration.DotNetTfm + "-" + platform.AsString ().ToLowerInvariant () + minSupportedOSVersion; + var supportedApiVersions = GetSupportedApiVersions (platform, isCompat: false); + + var project = "MultiTargetingLibrary"; + var project_path = GetProjectPath (project, platform: platform); + Clean (project_path); + var properties = GetDefaultProperties (); + properties ["cmdline:AllTheTargetFrameworks"] = targetFrameworks; + + if (IsTargetPlatformVersionCompatEnabled) { + var rv = DotNet.AssertBuild (project_path, properties); + var warnings = BinLog.GetBuildLogWarnings (rv.BinLogPath).ToArray (); + AssertWarningMessages (warnings, $"{minSupportedOSVersion} is not a valid TargetPlatformVersion for {platform.AsString ()}. This warning will become an error in future versions of the {platform.AsString ()} workload. Valid versions include:\n{string.Join ('\n', supportedApiVersions)}"); + } else { + var rv = DotNet.AssertBuildFailure (project_path, properties); + var errors = BinLog.GetBuildLogErrors (rv.BinLogPath).ToArray (); + AssertErrorMessages (errors, $"{minSupportedOSVersion} is not a valid TargetPlatformVersion for {platform.AsString ()}. Valid versions include:\n{string.Join ('\n', supportedApiVersions)}"); + } + } + + bool IsTargetPlatformVersionCompatEnabled { + get => Version.Parse (Configuration.DotNetTfm.Replace ("net", "")).Major < 9; + } + + string [] GetSupportedApiVersions (ApplePlatform platform, bool? isCompat = null) + { + if (isCompat is null) + isCompat = IsTargetPlatformVersionCompatEnabled; + if (isCompat.Value) + return GetSupportedTargetPlatformVersions (platform); + + var supportedApiVersions = Configuration.GetVariableArray ($"SUPPORTED_API_VERSIONS_{platform.AsString ().ToUpperInvariant ()}"); + return supportedApiVersions + .Where (v => v.StartsWith (Configuration.DotNetTfm + "-", StringComparison.Ordinal)) + .Select (v => v.Substring (Configuration.DotNetTfm.Length + 1)) + .ToArray (); + } + + string [] GetSupportedTargetPlatformVersions (ApplePlatform platform) + { + var plistPath = Path.Combine (Configuration.SourceRoot, "builds", $"Versions-{platform.AsString ()}.plist.in"); + var doc = new XmlDocument (); + doc.Load (plistPath); + var query = $"/plist/dict/key[text()='SupportedTargetPlatformVersions']/following-sibling::dict[1]/key[text()='{platform.AsString ()}']/following-sibling::array[1]/string"; + return doc + .SelectNodes (query)! + .Cast () + .Select (v => v.InnerText) + .ToArray (); } [Test] diff --git a/tests/dotnet/UnitTests/TestBaseClass.cs b/tests/dotnet/UnitTests/TestBaseClass.cs index 6d1052bdb70e..2d7829fb8bd3 100644 --- a/tests/dotnet/UnitTests/TestBaseClass.cs +++ b/tests/dotnet/UnitTests/TestBaseClass.cs @@ -422,23 +422,33 @@ public static void AssertErrorCount (IList errors, int count, str Assert.Fail ($"Expected {count} errors, got {errors.Count} errors: {message}.\n\t{string.Join ("\n\t", errors.Select (v => v.Message?.TrimEnd ()))}"); } - public static void AssertErrorMessages (IList errors, params string [] errorMessages) + public static void AssertWarningMessages (IList actualWarnings, params string [] expectedWarningMessages) { - if (errors.Count != errorMessages.Length) { - Assert.Fail ($"Expected {errorMessages.Length} errors, got {errors.Count} errors:\n\t{string.Join ("\n\t", errors.Select (v => v.Message?.TrimEnd ()))}"); + AssertBuildMessages ("warning", actualWarnings, expectedWarningMessages); + } + + public static void AssertErrorMessages (IList actualErrors, params string [] expectedErrorMessages) + { + AssertBuildMessages ("error", actualErrors, expectedErrorMessages); + } + + public static void AssertBuildMessages (string type, IList actualMessages, params string [] expectedMessages) + { + if (actualMessages.Count != expectedMessages.Length) { + Assert.Fail ($"Expected {expectedMessages.Length} {type}s, got {actualMessages.Count} {type}s:\n\t{string.Join ("\n\t", actualMessages.Select (v => v.Message?.TrimEnd ()))}"); return; } var failures = new List (); - for (var i = 0; i < errorMessages.Length; i++) { - if (errors [i].Message != errorMessages [i]) { - failures.Add ($"\tUnexpected error message #{i}:\n\t\tExpected: {errorMessages [i]}\n\t\tActual: {errors [i].Message?.TrimEnd ()}"); + for (var i = 0; i < expectedMessages.Length; i++) { + if (actualMessages [i].Message != expectedMessages [i]) { + failures.Add ($"\tUnexpected {type} message #{i}:\n\t\tExpected: {expectedMessages [i]}\n\t\tActual: {actualMessages [i].Message?.TrimEnd ()}"); } } if (!failures.Any ()) return; - Assert.Fail ($"Failure when comparing error messages:\n{string.Join ("\n", failures)}\n\tAll errors:\n\t\t{string.Join ("\n\t\t", errors.Select (v => v.Message?.TrimEnd ()))}"); + Assert.Fail ($"Failure when comparing {type} messages:\n{string.Join ("\n", failures)}\n\tAll {type}s:\n\t\t{string.Join ("\n\t\t", actualMessages.Select (v => v.Message?.TrimEnd ()))}"); } } }