From 423f095cdc690ebc803b18aa4cbbf95ebeed23ee Mon Sep 17 00:00:00 2001 From: tombogle Date: Fri, 18 Sep 2026 15:21:24 -0400 Subject: [PATCH 1/3] Retarget net40/net451 to net462 and clean up related package pins net40 has no known consumers anywhere in the sillsdev org, and net451 was already resolving to the nearest-lower fallback for every actively maintained .NET Framework consumer (FieldWorks, libpalaso, SayMore, LibChorus, HearThis), none of which target below net462. Replacing net451 with net462 matches those consumers directly. This also lets Microsoft.Extensions.DependencyModel unify on 10.0.9 across all target frameworks (it now officially supports net462), removing the net451-era pin to 2.1.0 and its accompanying Newtonsoft.Json override. System.ValueTuple is upgraded to 4.6.2 to match, since 4.6.2 requires net462 as its floor. Co-Authored-By: Claude Sonnet 5 --- .github/workflows/CI-CD.yml | 4 ++-- CHANGELOG.md | 29 +++++++++++++++++++++-- source/Directory.Build.props | 2 +- source/TestHelper/TestHelper.csproj | 2 +- source/icu.net.tests/icu.net.tests.csproj | 10 ++++---- source/icu.net/icu.net.csproj | 9 ++----- 6 files changed, 37 insertions(+), 19 deletions(-) diff --git a/.github/workflows/CI-CD.yml b/.github/workflows/CI-CD.yml index 6bb9e854..15439b49 100644 --- a/.github/workflows/CI-CD.yml +++ b/.github/workflows/CI-CD.yml @@ -65,9 +65,9 @@ jobs: - name: Test on .NET 10.0 run: dotnet test -p:TargetFramework=net10.0 --configuration Release --no-build source/icu.net.sln -- NUnit.TestOutputXml=TestResults - - name: Test on .NET Framework 4.6.1 (Windows only) + - name: Test on .NET Framework 4.6.2 (Windows only) if: ${{ matrix.os == 'windows-latest' }} - run: dotnet test -p:TargetFramework=net461 --configuration Release --no-build source/icu.net.sln -- NUnit.TestOutputXml=TestResults + run: dotnet test -p:TargetFramework=net462 --configuration Release --no-build source/icu.net.sln -- NUnit.TestOutputXml=TestResults - name: Upload Test Results if: always() diff --git a/CHANGELOG.md b/CHANGELOG.md index 15cf858c..efb68ac0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -25,6 +25,25 @@ and this project adheres to [Semantic Versioning](https://semver.org/). UVerticalOrientation, UIdentifierStatus, UIdentifierType. - Added net10.0 target framework. +### Changed + +- **BREAKING CHANGE:** Replaced the `net451` target framework with `net462`, matching the + framework already used by current consumers (e.g. libpalaso, LibChorus) and remaining the + nearest compatible target for others (FieldWorks, SayMore, HearThis). Any consumer targeting + `net461` or lower will no longer resolve a compatible .NET Framework build of this package + (falling back to the `netstandard2.0` build instead, if their tooling supports it). The + `net461`-targeted test/tooling projects in this repo were bumped to `net462` to match. +- Upgraded `System.ValueTuple` from 4.5.0 to 4.6.2. +- Unified `Microsoft.Extensions.DependencyModel` on version 10.0.9 across all target frameworks: + it now officially supports `net462`, so the old net451-era pin to 2.1.0 (and the accompanying + `Newtonsoft.Json` override) is no longer needed. +- Removed the explicit `Microsoft.SourceLink.GitHub` `PackageReference`: verified that the .NET + SDK (8.0+) now embeds correct `raw.githubusercontent.com` source link mappings for GitHub repos + without it — confirmed by inspecting the packed `.snupkg` PDBs for all four target frameworks. +- Removed the `System.IO.FileSystem` / `System.IO.FileSystem.Primitives` 4.3.0 references from + the test project: netstandard1.x-era facades, both deprecated on nuget.org, no longer needed + now that the lowest test target is `net462`. + ### Fixed - Fixed `Transliterator.Transliterate` throwing `OverflowException` for characters that expand @@ -85,12 +104,18 @@ and this project adheres to [Semantic Versioning](https://semver.org/). - In Character class, added \[Obsolete\] attribute to enum members UDecompositionType.COUNT and UNumericType.COUNT. +### Removed + +- **BREAKING CHANGE:** Dropped the `net40` target framework. A GitHub code search across + sillsdev's repos found no remaining consumers, and nothing in icu-dotnet's own test suite + exercised it either, but any project still building against `net40` specifically will no + longer find a compatible asset in this package. + ### Security - Upgraded `Microsoft.Extensions.DependencyModel` from 2.0.4 to 10.0.9 on non-.NET-Framework targets, eliminating the transitive dependency on `Newtonsoft.Json` 9.0.1 (high severity - vulnerability). The `net451` target retains `Microsoft.Extensions.DependencyModel` 2.1.0 (the - newest version with net451 support) and pins the latest `Newtonsoft.Json`. + vulnerability). - Upgraded `SIL.ReleaseTasks` to 4.0.0, which upgrades its own `SIL.Core` dependency to bring `Newtonsoft.Json` to 13.0.1 — past the vulnerability (GHSA-5crp-9r3c-p9vr) — and no longer diff --git a/source/Directory.Build.props b/source/Directory.Build.props index d4799101..8f58d947 100644 --- a/source/Directory.Build.props +++ b/source/Directory.Build.props @@ -1,6 +1,6 @@ - net40;net451;netstandard2.0;net8.0;net10.0 + net462;netstandard2.0;net8.0;net10.0 netstandard $(MSBuildThisFileDirectory)\..\output\$(Configuration) $(MSBuildThisFileDirectory)\..\output diff --git a/source/TestHelper/TestHelper.csproj b/source/TestHelper/TestHelper.csproj index 5215f0f6..bf4ba8bc 100644 --- a/source/TestHelper/TestHelper.csproj +++ b/source/TestHelper/TestHelper.csproj @@ -1,6 +1,6 @@ - net461;net8.0;net10.0 + net462;net8.0;net10.0 ../../output/$(Configuration)/TestHelper Exe Icu.Tests diff --git a/source/icu.net.tests/icu.net.tests.csproj b/source/icu.net.tests/icu.net.tests.csproj index 12b8f328..4d5a15c7 100644 --- a/source/icu.net.tests/icu.net.tests.csproj +++ b/source/icu.net.tests/icu.net.tests.csproj @@ -3,9 +3,9 @@ - net461;net8.0;net10.0 + net462;net8.0;net10.0 Icu.Tests icu.net.tests false @@ -17,15 +17,13 @@ - - diff --git a/source/icu.net/icu.net.csproj b/source/icu.net/icu.net.csproj index 4902b4b3..c79b0ea9 100644 --- a/source/icu.net/icu.net.csproj +++ b/source/icu.net/icu.net.csproj @@ -8,15 +8,10 @@ - - + - - - - - + From dd70ffc41223f6b47214c11a6cd5ca5c746a48e9 Mon Sep 17 00:00:00 2001 From: tombogle Date: Mon, 21 Sep 2026 17:33:58 -0400 Subject: [PATCH 2/3] Remove dead NET40 conditionals and stale net461/4.5.1 comments Now that net40 is dropped entirely as a target framework, the NET40 symbol is never defined, making every #if NET40 / #if !NET40 branch permanently dead code. Collapse each to its always-taken branch and drop the now-moot explanatory comments (.NET 4.5/4.5.1 API availability notes). Also update leftover net461-specific comments left over from the net462 retarget. Addresses reviewer feedback on PR #235. Co-Authored-By: Claude Sonnet 5 --- CHANGELOG.md | 4 ++++ source/icu.net.tests/CharacterTests.cs | 4 ---- source/icu.net.tests/LocaleTests.cs | 8 -------- source/icu.net.tests/MessageFormatterTests.cs | 2 +- .../NativeMethods/NativeMethodsHelperTests.cs | 2 -- .../NativeMethods/NativeMethodsTests.cs | 6 ++---- source/icu.net/NativeMethods/NativeMethods.cs | 13 ------------- source/icu.net/NativeMethods/NativeMethodsHelper.cs | 8 -------- 8 files changed, 7 insertions(+), 40 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a754d307..0a01c897 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -111,6 +111,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/). sillsdev's repos found no remaining consumers, and nothing in icu-dotnet's own test suite exercised it either, but any project still building against `net40` specifically will no longer find a compatible asset in this package. +- Removed dead `#if NET40` / `#if !NET40` conditional code (and the comments explaining it) from + `NativeMethods.cs`, `NativeMethodsHelper.cs`, `NativeMethodsTests.cs`, `NativeMethodsHelperTests.cs`, + `CharacterTests.cs`, and `LocaleTests.cs`, now that `net40` is no longer a target framework. + Also updated remaining `net461`/`4.5.1`-specific comments to reflect the `net462` retarget. ### Security diff --git a/source/icu.net.tests/CharacterTests.cs b/source/icu.net.tests/CharacterTests.cs index b333170b..17b6ec58 100644 --- a/source/icu.net.tests/CharacterTests.cs +++ b/source/icu.net.tests/CharacterTests.cs @@ -12,11 +12,7 @@ public class CharacterTests private void SetUICulture(string culture) { var cultureInfo = new CultureInfo(culture); -#if NET40 - System.Threading.Thread.CurrentThread.CurrentUICulture = cultureInfo; -#else CultureInfo.CurrentUICulture = cultureInfo; -#endif } // valid digit tests diff --git a/source/icu.net.tests/LocaleTests.cs b/source/icu.net.tests/LocaleTests.cs index 57a92ef5..d0da36f8 100644 --- a/source/icu.net.tests/LocaleTests.cs +++ b/source/icu.net.tests/LocaleTests.cs @@ -303,21 +303,13 @@ public void ImplicitCast() private void SetUICulture(string culture) { var cultureInfo = new CultureInfo(culture); -#if NET40 - System.Threading.Thread.CurrentThread.CurrentUICulture = cultureInfo; -#else CultureInfo.CurrentUICulture = cultureInfo; -#endif } private void SetCulture(string culture) { var cultureInfo = new CultureInfo(culture); -#if NET40 - System.Threading.Thread.CurrentThread.CurrentCulture = cultureInfo; -#else CultureInfo.CurrentCulture = cultureInfo; -#endif } } } diff --git a/source/icu.net.tests/MessageFormatterTests.cs b/source/icu.net.tests/MessageFormatterTests.cs index cfaf1c7a..7bb48a6a 100644 --- a/source/icu.net.tests/MessageFormatterTests.cs +++ b/source/icu.net.tests/MessageFormatterTests.cs @@ -29,7 +29,7 @@ public class MessageFormatterTests // Skip when umsg_format produces wrong results due to the non-ARM64 Unix ICU 74+ // double-varargs ABI mismatch (https://github.com/dotnet/runtime/issues/48752). - // net461 only runs on Windows, so the check is unnecessary there. + // .NET Framework tests only run on Windows, so the check is unnecessary there. private static void SkipIfUnreliableOnThisPlatform() { #if !NETFRAMEWORK diff --git a/source/icu.net.tests/NativeMethods/NativeMethodsHelperTests.cs b/source/icu.net.tests/NativeMethods/NativeMethodsHelperTests.cs index 2e41aff4..879969de 100644 --- a/source/icu.net.tests/NativeMethods/NativeMethodsHelperTests.cs +++ b/source/icu.net.tests/NativeMethods/NativeMethodsHelperTests.cs @@ -7,7 +7,6 @@ namespace Icu.Tests { -#if !NET40 [TestFixture] public class NativeMethodsHelperTests { @@ -71,5 +70,4 @@ public void GetIcuVersionInfoForNetCoreOrWindows_DoesNotCrash() Assert.That(result, Is.EqualTo(Wrapper.MaxSupportedIcuVersion)); } } -#endif } diff --git a/source/icu.net.tests/NativeMethods/NativeMethodsTests.cs b/source/icu.net.tests/NativeMethods/NativeMethodsTests.cs index aea8b574..90e348a1 100644 --- a/source/icu.net.tests/NativeMethods/NativeMethodsTests.cs +++ b/source/icu.net.tests/NativeMethods/NativeMethodsTests.cs @@ -53,9 +53,7 @@ internal static string GetArchSubdir(string prefix = "") internal static string OutputDirectory => Path.GetDirectoryName( new Uri( -#if NET40 - typeof(NativeMethodsTests).Assembly.CodeBase -#elif NET5_0_OR_GREATER +#if NET5_0_OR_GREATER typeof(NativeMethodsTests).GetTypeInfo().Assembly.Location #else typeof(NativeMethodsTests).GetTypeInfo().Assembly.CodeBase @@ -122,7 +120,7 @@ private static void CopyMinimalIcuFiles(string targetDir) internal static void CopyTestFiles(string sourceDir, string targetDir) { - // sourceDir is something like output/Debug/net461, TestHelper is in output/Debug/TestHelper/net461 + // sourceDir is something like output/Debug/net*, TestHelper is in output/Debug/TestHelper/net* var framework = Path.GetFileName(sourceDir); sourceDir = Path.Combine(sourceDir, "..", "TestHelper", framework); CopyFilesFromDirectory(sourceDir, targetDir); diff --git a/source/icu.net/NativeMethods/NativeMethods.cs b/source/icu.net/NativeMethods/NativeMethods.cs index 5584e37f..68284eee 100644 --- a/source/icu.net/NativeMethods/NativeMethods.cs +++ b/source/icu.net/NativeMethods/NativeMethods.cs @@ -167,12 +167,7 @@ internal static string DirectoryOfThisAssembly { get { - //NOTE: .GetTypeInfo() is not supported until .NET 4.5 onwards. -#if NET40 - var currentAssembly = typeof(NativeMethods).Assembly; -#else var currentAssembly = typeof(NativeMethods).GetTypeInfo().Assembly; -#endif #if NET var managedPath = currentAssembly.Location; // If the application is published as a single file, Assembly.Location will be an empty string. @@ -538,7 +533,6 @@ private static void ResetIcuVersionInfo() IcuVersion = 0; _IcuPath = null; -#if !NET40 NativeMethodsHelper.Reset(); var icuInfo = NativeMethodsHelper.GetIcuVersionInfoForNetCoreOrWindows(); @@ -547,7 +541,6 @@ private static void ResetIcuVersionInfo() _IcuPath = icuInfo.IcuPath.FullName; IcuVersion = icuInfo.IcuVersion; } -#endif } // This method is thread-safe and idempotent @@ -593,13 +586,7 @@ private static T GetMethod(IntPtr handle, string methodName, bool missingInMi if (methodPointer != IntPtr.Zero) { - // NOTE: Starting in .NET 4.5.1, Marshal.GetDelegateForFunctionPointer(IntPtr, Type) is obsolete. -#if NET40 - return Marshal.GetDelegateForFunctionPointer( - methodPointer, typeof(T)) as T; -#else return Marshal.GetDelegateForFunctionPointer(methodPointer); -#endif } if (missingInMinimal) { diff --git a/source/icu.net/NativeMethods/NativeMethodsHelper.cs b/source/icu.net/NativeMethods/NativeMethodsHelper.cs index 1ff65cae..ff2b800c 100644 --- a/source/icu.net/NativeMethods/NativeMethodsHelper.cs +++ b/source/icu.net/NativeMethods/NativeMethodsHelper.cs @@ -5,9 +5,7 @@ using System.IO; using System.Linq; using System.Text.RegularExpressions; -#if !NET40 using Microsoft.Extensions.DependencyModel; -#endif // ReSharper disable once CheckNamespace namespace Icu @@ -76,7 +74,6 @@ public static IcuVersionInfo GetIcuVersionInfoForNetCoreOrWindows() return IcuVersion; } -#if !NET40 var context = DependencyContext.Default; // If this is false, something went wrong. These files should have // either been found above or we should have been able to locate the @@ -103,7 +100,6 @@ public static IcuVersionInfo GetIcuVersionInfoForNetCoreOrWindows() } TrySetIcuPathFromDirectory(new DirectoryInfo(packagePath), nativeAssetPaths); -#endif return IcuVersion; } @@ -169,14 +165,12 @@ private static bool TryGetPathFromAssemblyDirectory() } string[] nativeAssetPaths = null; -#if !NET40 // 3. Check in {directoryOfAssembly}/runtimes/{runtimeId}/native/ if (!TryGetNativeAssetPaths(DependencyContext.Default, out nativeAssetPaths)) { Trace.WriteLine("Could not locate icu native assets from DependencyModel."); return false; } -#endif // If we found the icu*.dll files under {directoryOfAssembly}/runtimes/{rid}/native/, // they should ALL be there... or else something went wrong in publishing the app or // restoring the files, or packaging the NuGet package. @@ -258,7 +252,6 @@ private static bool TrySetIcuPathFromDirectory(DirectoryInfo baseDirectory, stri return doAllAssetsExistInDirectory; } -#if !NET40 /// /// Tries to get the icu native binaries by searching the Runtime /// ID graph to find the first set of paths that have those binaries. @@ -317,7 +310,6 @@ private static bool TryResolvePackagePath(CompilationLibrary library, string bas return Directory.Exists(packagePath); } -#endif /// /// Tries to fetch the default package directory for NuGet packages. From 6402b102a36436c359c71c3cd8c7f8d47f50e5be Mon Sep 17 00:00:00 2001 From: tombogle Date: Mon, 21 Sep 2026 18:49:13 -0400 Subject: [PATCH 3/3] Fix stale net461 path in icu-dotnet.proj's TestAssemblies Two RunNUnitTC/RunNUnit targets still pointed at the net461 output directory after the net462 retarget. This file is deleted entirely in the next stacked PR, but keep it internally consistent in the meantime. Co-Authored-By: Claude Sonnet 5 --- build/icu-dotnet.proj | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/build/icu-dotnet.proj b/build/icu-dotnet.proj index 29d8740d..5b2a0904 100644 --- a/build/icu-dotnet.proj +++ b/build/icu-dotnet.proj @@ -79,7 +79,7 @@ - + - +