From 0ab57ff02027cb9d1248ec8fd17f47952a5d3e13 Mon Sep 17 00:00:00 2001 From: Eberhard Beilharz Date: Wed, 3 Aug 2022 16:43:26 +0200 Subject: [PATCH] Allow building with .net 6.0 - Add net6.0 to tests - allow to build and run tests with `dotnet` - fix failing tests with net6.0 - at least partially caused by a too long message for `IgnoreAttribute` - move native tests to `NativeMethods` subdirectory - remove `icu.net.netstandard.testrunner` project since it's now possible to run .NET Core tests with `dotnet test` - implement `DllResolver` to resolve native libraries on .NET Core +semver:minor --- CHANGELOG.md | 4 + README.md | 48 ++++++------ source/Directory.Build.props | 4 +- source/TestHelper/TestHelper.csproj | 2 +- .../icu.net.netstandard.testrunner/Program.cs | 17 ----- .../icu.net.netstandard.testrunner.csproj | 14 ---- source/icu.net.sln | 9 +-- .../BreakIteratorTests.JDKCompatibility.cs | 15 ++-- .../icu.net.tests/Collation/SortKeyTests.cs | 4 +- .../NativeMethods/DllResolverTests.cs | 63 ++++++++++++++++ .../NativeMethodsHelperTests.cs | 0 .../{ => NativeMethods}/NativeMethodsTests.cs | 0 source/icu.net.tests/SetUpFixture.cs | 3 +- source/icu.net.tests/icu.net.tests.csproj | 3 +- source/icu.net/Collation/Collator.cs | 40 ++++++---- source/icu.net/Collation/RuleBasedCollator.cs | 2 +- source/icu.net/IcuWrapper.cs | 3 + source/icu.net/NativeMethods/DllResolver.cs | 74 +++++++++++++++++++ source/icu.net/NativeMethods/NativeMethods.cs | 9 +++ source/icu.net/SafeEnumeratorHandle.cs | 2 +- source/icu.net/Transliterator.cs | 2 +- 21 files changed, 223 insertions(+), 95 deletions(-) delete mode 100644 source/icu.net.netstandard.testrunner/Program.cs delete mode 100644 source/icu.net.netstandard.testrunner/icu.net.netstandard.testrunner.csproj create mode 100644 source/icu.net.tests/NativeMethods/DllResolverTests.cs rename source/icu.net.tests/{ => NativeMethods}/NativeMethodsHelperTests.cs (100%) rename source/icu.net.tests/{ => NativeMethods}/NativeMethodsTests.cs (100%) create mode 100644 source/icu.net/NativeMethods/DllResolver.cs diff --git a/CHANGELOG.md b/CHANGELOG.md index 55e0a2c0..036248f6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,6 +16,10 @@ and this project adheres to [Semantic Versioning](http://semver.org/). ## [Unreleased] +### Added + +- Support .net 6.0 + ## [2.8.1] - 2022-07-08 ### Fixed diff --git a/README.md b/README.md index 2ecd74e4..03f8db2b 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,10 @@ # Overview icu-dotnet is the C# wrapper for a subset of [ICU](https://icu.unicode.org/). ->ICU is a mature, widely used set of C/C++ and Java libraries providing Unicode and Globalization support for software applications. ICU is widely portable and gives applications the same results on all platforms and between C/C++ and Java software. + +> ICU is a mature, widely used set of C/C++ and Java libraries providing Unicode and Globalization support +> for software applications. ICU is widely portable and gives applications the same results on all platforms +> and between C/C++ and Java software. ## Status @@ -28,31 +31,32 @@ Similarly, it might be beneficial to call `Icu.Wrapper.Cleanup()` before exiting Sample code: ``` csharp - static class Program - { - public static void Main(string[] args) - { - Icu.Wrapper.Init(); - // Will output "NFC form of XA\u0308bc is XÄbc" - Console.WriteLine($"NFC form of XA\\u0308bc is {Icu.Normalizer.Normalize("XA\u0308bc", - Icu.Normalizer.UNormalizationMode.UNORM_NFC)}"); - Icu.Wrapper.Cleanup(); - } - } + static class Program + { + public static void Main(string[] args) + { + Icu.Wrapper.Init(); + // Will output "NFC form of XA\u0308bc is XÄbc" + Console.WriteLine($"NFC form of XA\\u0308bc is {Icu.Normalizer.Normalize("XA\u0308bc", + Icu.Normalizer.UNormalizationMode.UNORM_NFC)}"); + Icu.Wrapper.Cleanup(); + } + } ``` ## Building -icu-dotnet can be built with Visual Studio or MonoDevelop, but at least initially it might be -easier to build from the command line because that will download all necessary dependencies. +To build the current version of icu-dotnet you'll need .net 6.0 installed. -### Linux +icu-dotnet can be built from the command line as well as Visual Studio or JetBrains Rider. -You can build and run the unit tests by running: +### Windows and Linux - build/TestBuild.sh +You can build and run the unit tests by running: -If you run into issues you might want to try with a newer mono version or with our custom `mono-sil` package from [packages.sil.org](http://packages.sil.org/) +```bash +dotnet test source/icu.net.sln +``` ### Docker @@ -72,12 +76,6 @@ RUN apt-get update \ ... ``` -### Windows - -Build and run the unit tests by running: - - msbuild /t:Test build/icu-dotnet.proj - ## ICU versions ### Linux @@ -118,7 +116,7 @@ The package installer should have added an import to the `*.csproj` file similar ```xml + Condition="Exists('..\..\packages\Icu4c.Win.Min.54.1.31\build\Icu4c.Win.Min.targets')" /> ``` ## Contributing diff --git a/source/Directory.Build.props b/source/Directory.Build.props index 55dc81d9..e5be2d5c 100644 --- a/source/Directory.Build.props +++ b/source/Directory.Build.props @@ -1,6 +1,6 @@ - net40;net451;netstandard1.6 + net40;net451;netstandard1.6;net6.0 netstandard $(MSBuildThisFileDirectory)\..\output\$(Configuration) $(MSBuildThisFileDirectory)\..\output @@ -17,7 +17,7 @@ See full changelog at https://github.com/sillsdev/icu-dotnet/blob/master/CHANGEL $(MSBuildThisFileDirectory)/../CHANGELOG.md true snupkg - 7.1 + default true true $(MSBuildThisFileDirectory)/icu.net.snk diff --git a/source/TestHelper/TestHelper.csproj b/source/TestHelper/TestHelper.csproj index 9d7d774f..f221cf4d 100644 --- a/source/TestHelper/TestHelper.csproj +++ b/source/TestHelper/TestHelper.csproj @@ -1,6 +1,6 @@ - net461;netcoreapp3.1 + net461;net6.0 ../../output/$(Configuration)/TestHelper Exe Icu.Tests diff --git a/source/icu.net.netstandard.testrunner/Program.cs b/source/icu.net.netstandard.testrunner/Program.cs deleted file mode 100644 index c3de8a4f..00000000 --- a/source/icu.net.netstandard.testrunner/Program.cs +++ /dev/null @@ -1,17 +0,0 @@ -// Copyright (c) 2013 SIL International -// This software is licensed under the MIT license (http://opensource.org/licenses/MIT) -using Icu.Tests; -using NUnitLite; -using System.Reflection; - -namespace icu.net.netstandard.testrunner -{ - class Program - { - public static int Main(string[] args) - { - var assembly = typeof(BreakIteratorTests).GetTypeInfo().Assembly; - return new AutoRun(assembly).Execute(args); - } - } -} \ No newline at end of file diff --git a/source/icu.net.netstandard.testrunner/icu.net.netstandard.testrunner.csproj b/source/icu.net.netstandard.testrunner/icu.net.netstandard.testrunner.csproj deleted file mode 100644 index 9da597c5..00000000 --- a/source/icu.net.netstandard.testrunner/icu.net.netstandard.testrunner.csproj +++ /dev/null @@ -1,14 +0,0 @@ - - - Exe - - netcoreapp3.1 - false - - - - - - - - diff --git a/source/icu.net.sln b/source/icu.net.sln index 86e5b662..5b2dc09c 100644 --- a/source/icu.net.sln +++ b/source/icu.net.sln @@ -10,16 +10,15 @@ EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution items", "Solution items", "{ADD2D664-A179-47DE-BE36-94AF75330E29}" ProjectSection(SolutionItems) = preProject ..\CHANGELOG.md = ..\CHANGELOG.md - .nuget\packages.config = .nuget\packages.config ..\.editorconfig = ..\.editorconfig ..\GitVersion.yml = ..\GitVersion.yml ..\README.md = ..\README.md + Directory.Build.props = Directory.Build.props + Directory.Build.targets = Directory.Build.targets EndProjectSection EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TestHelper", "TestHelper\TestHelper.csproj", "{F195ADCE-9129-446E-85E0-8EEAD01ED08D}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "icu.net.netstandard.testrunner", "icu.net.netstandard.testrunner\icu.net.netstandard.testrunner.csproj", "{30997039-1AD1-4A11-91EE-F53A41788FB5}" -EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -38,10 +37,6 @@ Global {F195ADCE-9129-446E-85E0-8EEAD01ED08D}.Debug|Any CPU.Build.0 = Debug|Any CPU {F195ADCE-9129-446E-85E0-8EEAD01ED08D}.Release|Any CPU.ActiveCfg = Release|Any CPU {F195ADCE-9129-446E-85E0-8EEAD01ED08D}.Release|Any CPU.Build.0 = Release|Any CPU - {30997039-1AD1-4A11-91EE-F53A41788FB5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {30997039-1AD1-4A11-91EE-F53A41788FB5}.Debug|Any CPU.Build.0 = Debug|Any CPU - {30997039-1AD1-4A11-91EE-F53A41788FB5}.Release|Any CPU.ActiveCfg = Release|Any CPU - {30997039-1AD1-4A11-91EE-F53A41788FB5}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/source/icu.net.tests/BreakIteratorTests.JDKCompatibility.cs b/source/icu.net.tests/BreakIteratorTests.JDKCompatibility.cs index 238fa714..7556f5bd 100644 --- a/source/icu.net.tests/BreakIteratorTests.JDKCompatibility.cs +++ b/source/icu.net.tests/BreakIteratorTests.JDKCompatibility.cs @@ -8,11 +8,12 @@ namespace Icu.Tests /// /// Tests to ensure compatibility with JDK 7 /// + // Ignore tests until a JavaBreakIterator using RuleBasedBreakIterator is written. The rules for JavaBreakIterator can be found at: + // http://grepcode.com/file/repository.grepcode.com/java/root/jdk/openjdk/7u40-b43/sun/text/resources/BreakIteratorRules.java/ + // and http://grepcode.com/file/repository.grepcode.com/java/root/jdk/openjdk/7u40-b43/sun/text/resources/BreakIteratorRules_th.java#BreakIteratorRules_th [TestFixture] [Category("Full ICU")] - [Ignore("Ignore tests until a JavaBreakIterator using RuleBasedBreakIterator is written. The rules for JavaBreakIterator can be found at:\n" - + "http://grepcode.com/file/repository.grepcode.com/java/root/jdk/openjdk/7u40-b43/sun/text/resources/BreakIteratorRules.java/ \n" - + "and http://grepcode.com/file/repository.grepcode.com/java/root/jdk/openjdk/7u40-b43/sun/text/resources/BreakIteratorRules_th.java#BreakIteratorRules_th")] + [Ignore("Needs implementation of JavaBreakIterator using RuleBasedBreakIterator. See comment in code.")] public class BreakIteratorTests_JDKCompatibility { static readonly String TEXT = @@ -26,7 +27,7 @@ private BreakIterator GetWordInstance(System.Globalization.CultureInfo locale) [Test] public void TestWordIteration() { - BreakIterator bi = GetWordInstance(System.Globalization.CultureInfo.InvariantCulture); + using var bi = GetWordInstance(System.Globalization.CultureInfo.InvariantCulture); // Test empty Assert.AreEqual(0, bi.Current); @@ -103,7 +104,7 @@ public void TestWordIteration() [Test] public void TestWordIterationThai() { - BreakIterator bi = GetWordInstance(new System.Globalization.CultureInfo("th")); + using var bi = GetWordInstance(new System.Globalization.CultureInfo("th")); // Test empty Assert.AreEqual(0, bi.Current); @@ -184,7 +185,7 @@ private BreakIterator GetSentenceInstance(System.Globalization.CultureInfo local [Test] public void TestSentenceIteration() { - BreakIterator bi = GetSentenceInstance(System.Globalization.CultureInfo.InvariantCulture); + using var bi = GetSentenceInstance(System.Globalization.CultureInfo.InvariantCulture); // Test empty Assert.AreEqual(0, bi.Current); @@ -265,7 +266,7 @@ private BreakIterator GetLineInstance(System.Globalization.CultureInfo locale) [Test] public void TestLineIteration() { - BreakIterator bi = GetLineInstance(System.Globalization.CultureInfo.InvariantCulture); + using var bi = GetLineInstance(System.Globalization.CultureInfo.InvariantCulture); // Test empty Assert.AreEqual(0, bi.Current); diff --git a/source/icu.net.tests/Collation/SortKeyTests.cs b/source/icu.net.tests/Collation/SortKeyTests.cs index af68cb48..868b7e20 100644 --- a/source/icu.net.tests/Collation/SortKeyTests.cs +++ b/source/icu.net.tests/Collation/SortKeyTests.cs @@ -106,7 +106,7 @@ public void Compare_SamePrefixSecondLonger_precedes() SortKey sortKey1 = Collator.CreateSortKey("heo", keyData1); byte[] keyData2 = new byte[] { 0xae, 0x1, 0x20, 0x32, 0x1 }; SortKey sortKey2 = Collator.CreateSortKey("heol", keyData2); - Assert.AreEqual(Precedes, SortKey.Compare(sortKey1, sortKey2)); + Assert.That(SortKey.Compare(sortKey1, sortKey2), Is.LessThanOrEqualTo(Precedes)); } [Test] @@ -116,7 +116,7 @@ public void Compare_SamePrefixSecondShorter_follows() SortKey sortKey1 = Collator.CreateSortKey("heol", keyData1); byte[] keyData2 = new byte[] { 0xae, 0x1, 0x20, 0x1 }; SortKey sortKey2 = Collator.CreateSortKey("heo", keyData2); - Assert.AreEqual(Follows, SortKey.Compare(sortKey1, sortKey2)); + Assert.That(SortKey.Compare(sortKey1, sortKey2), Is.GreaterThanOrEqualTo(Follows)); } diff --git a/source/icu.net.tests/NativeMethods/DllResolverTests.cs b/source/icu.net.tests/NativeMethods/DllResolverTests.cs new file mode 100644 index 00000000..98da3729 --- /dev/null +++ b/source/icu.net.tests/NativeMethods/DllResolverTests.cs @@ -0,0 +1,63 @@ +// Copyright (c) 2022 SIL International +// This software is licensed under the MIT License (http://opensource.org/licenses/MIT) + +using System.IO; +using NUnit.Framework; + +namespace Icu.Tests +{ +#if NET + [TestFixture] + public class DllResolverTests + { + private string _tempPath; + + [TearDown] + public void TearDown() + { + if (string.IsNullOrEmpty(_tempPath)) + return; + + Directory.Delete(_tempPath, true); + _tempPath = null; + } + + [TestCase("linux", ExpectedResult = true, IncludePlatform = "Linux")] + [TestCase("Linux", ExpectedResult = true, IncludePlatform = "Linux")] + [TestCase("!windows,osx", ExpectedResult = true, IncludePlatform = "Linux")] + [TestCase("!windows,linux", ExpectedResult = false, IncludePlatform = "Linux, Win")] + [TestCase("windows,linux", ExpectedResult = true, IncludePlatform = "Linux")] + [TestCase("windows", ExpectedResult = true, IncludePlatform = "Win")] + [TestCase("Windows", ExpectedResult = true, IncludePlatform = "Win")] + [TestCase("!linux,osx", ExpectedResult = true, IncludePlatform = "Win")] + [TestCase("windows,linux", ExpectedResult = true, IncludePlatform = "Win")] + [TestCase("osx", ExpectedResult = true, IncludePlatform = "MacOsX")] + [TestCase("OSX", ExpectedResult = true, IncludePlatform = "MacOsX")] + [TestCase("!linux,windows", ExpectedResult = true, IncludePlatform = "MacOsX")] + [TestCase("!windows,osx", ExpectedResult = false, IncludePlatform = "MacOsX, Win")] + [TestCase("windows,osx", ExpectedResult = true, IncludePlatform = "MacOsX")] + public bool ConditionApplies(string condition) + { + return DllResolver.ConditionApplies(condition); + } + + [TestCase(ExpectedResult = "libdl.so.2", IncludePlatform = "Linux")] + [TestCase(ExpectedResult = "libdl.dylib", IncludePlatform = "MacOsX")] + [TestCase(ExpectedResult = "libdl.dll", IncludePlatform = "Win")] + public string MapLibraryName() + { + _tempPath = Path.Combine(Path.GetTempPath(), Path.GetRandomFileName()); + Directory.CreateDirectory(_tempPath); + var fakeDll = Path.Combine(_tempPath, "test.dll"); + var configFile = fakeDll + ".config"; + File.WriteAllText(configFile, @" + + + +"); + + return DllResolver.MapLibraryName(fakeDll, "libdl.dll"); + } + } +#endif +} diff --git a/source/icu.net.tests/NativeMethodsHelperTests.cs b/source/icu.net.tests/NativeMethods/NativeMethodsHelperTests.cs similarity index 100% rename from source/icu.net.tests/NativeMethodsHelperTests.cs rename to source/icu.net.tests/NativeMethods/NativeMethodsHelperTests.cs diff --git a/source/icu.net.tests/NativeMethodsTests.cs b/source/icu.net.tests/NativeMethods/NativeMethodsTests.cs similarity index 100% rename from source/icu.net.tests/NativeMethodsTests.cs rename to source/icu.net.tests/NativeMethods/NativeMethodsTests.cs diff --git a/source/icu.net.tests/SetUpFixture.cs b/source/icu.net.tests/SetUpFixture.cs index 0b2a4e15..f160ee93 100644 --- a/source/icu.net.tests/SetUpFixture.cs +++ b/source/icu.net.tests/SetUpFixture.cs @@ -2,6 +2,7 @@ // This software is licensed under the MIT license (http://opensource.org/licenses/MIT) using System; +using System.Runtime.InteropServices; using NUnit.Framework; namespace Icu.Tests @@ -15,7 +16,7 @@ private static bool IsWindows { // See Icu.Platform. Unfortunately that's internal, so we can't use it. -#if !NETSTANDARD1_6 +#if !NETSTANDARD1_6 && !NET // See http://www.mono-project.com/docs/faq/technical/#how-to-detect-the-execution-platform switch ((int)Environment.OSVersion.Platform) { diff --git a/source/icu.net.tests/icu.net.tests.csproj b/source/icu.net.tests/icu.net.tests.csproj index 655c82d8..d87410b3 100644 --- a/source/icu.net.tests/icu.net.tests.csproj +++ b/source/icu.net.tests/icu.net.tests.csproj @@ -1,10 +1,9 @@ - net461;netcoreapp3.1 + net461;net6.0 Icu.Tests icu.net.tests false - 8 diff --git a/source/icu.net/Collation/Collator.cs b/source/icu.net/Collation/Collator.cs index bc1537cd..5f27ef08 100644 --- a/source/icu.net/Collation/Collator.cs +++ b/source/icu.net/Collation/Collator.cs @@ -192,12 +192,12 @@ static public SortKey CreateSortKey(string originalString, byte[] keyData, int k throw new ArgumentOutOfRangeException("keyDataLength"); } - CompareOptions options = CompareOptions.None; + var options = CompareOptions.None; #if NETSTANDARD1_6 - SortKey sortKey = new SortKey(CultureInfo.InvariantCulture.Name, originalString, options, keyData); + var sortKey = new SortKey(CultureInfo.InvariantCulture.Name, originalString, options, keyData); #else - SortKey sortKey = CultureInfo.InvariantCulture.CompareInfo.GetSortKey(string.Empty, options); + var sortKey = CultureInfo.InvariantCulture.CompareInfo.GetSortKey(string.Empty, options); SetInternalOriginalStringField(sortKey, originalString); SetInternalKeyDataField(sortKey, keyData, keyDataLength); #endif @@ -208,14 +208,16 @@ static public SortKey CreateSortKey(string originalString, byte[] keyData, int k #if !NETSTANDARD1_6 private static void SetInternalKeyDataField(SortKey sortKey, byte[] keyData, int keyDataLength) { - byte[] keyDataCopy = new byte[keyDataLength]; + var keyDataCopy = new byte[keyDataLength]; Array.Copy(keyData, keyDataCopy, keyDataLength); - string propertyName = "SortKey.KeyData"; - string monoInternalFieldName = "key"; - string netInternalFieldName = "m_KeyData"; + var propertyName = "SortKey.KeyData"; + var monoInternalFieldName = "key"; + var frameworkInternalFieldName = "m_KeyData"; + var netInternalFieldName = "_keyData"; SetInternalFieldForPublicProperty(sortKey, propertyName, + frameworkInternalFieldName, netInternalFieldName, monoInternalFieldName, keyDataCopy); @@ -224,11 +226,13 @@ private static void SetInternalKeyDataField(SortKey sortKey, byte[] keyData, int private static void SetInternalOriginalStringField(SortKey sortKey, string originalString) { - string propertyName = "SortKey.OriginalString"; - string monoInternalFieldName = "source"; - string netInternalFieldName = "m_String"; + var propertyName = "SortKey.OriginalString"; + var monoInternalFieldName = "source"; + var frameworkInternalFieldName = "m_String"; + var netInternalFieldName = "_string"; SetInternalFieldForPublicProperty(sortKey, propertyName, + frameworkInternalFieldName, netInternalFieldName, monoInternalFieldName, originalString); @@ -237,15 +241,23 @@ private static void SetInternalOriginalStringField(SortKey sortKey, string origi private static void SetInternalFieldForPublicProperty( T instance, string propertyName, + string frameworkInternalFieldName, string netInternalFieldName, string monoInternalFieldName, P value) { - Type type = instance.GetType(); + var type = instance.GetType(); - string fieldName = IsRunningOnMono() ? monoInternalFieldName : netInternalFieldName; + var fieldName = IsRunningOnMono() ? monoInternalFieldName : frameworkInternalFieldName; - FieldInfo fieldInfo = type.GetField(fieldName, BindingFlags.Instance | BindingFlags.NonPublic); +#if NET + if (RuntimeInformation.FrameworkDescription != ".NET Framework") + fieldName = netInternalFieldName; +#endif + + var fields = type.GetFields(BindingFlags.Instance | BindingFlags.NonPublic); + + var fieldInfo = type.GetField(fieldName, BindingFlags.Instance | BindingFlags.NonPublic); Debug.Assert(fieldInfo != null, "Unsupported runtime", @@ -351,7 +363,7 @@ public static string GetCollationRules(Locale locale, UColRuleOption collatorRul public static void GetSortKeyBound(byte[] sortKey, UColBoundMode boundType, ref byte[] result) { ErrorCode err; - int size = NativeMethods.ucol_getBound(sortKey, sortKey.Length, boundType, 1, result, result.Length, out err); + var size = NativeMethods.ucol_getBound(sortKey, sortKey.Length, boundType, 1, result, result.Length, out err); if (err > 0 && err != ErrorCode.BUFFER_OVERFLOW_ERROR) throw new Exception("Collator.GetSortKeyBound() failed with code " + err); if (size > result.Length) diff --git a/source/icu.net/Collation/RuleBasedCollator.cs b/source/icu.net/Collation/RuleBasedCollator.cs index 7610f7e6..9d56158b 100644 --- a/source/icu.net/Collation/RuleBasedCollator.cs +++ b/source/icu.net/Collation/RuleBasedCollator.cs @@ -30,7 +30,7 @@ public SafeRuleBasedCollatorHandle() : /// true if the handle is released successfully; otherwise, in the event of a catastrophic failure, false. /// In this case, it generates a ReleaseHandleFailed Managed Debugging Assistant. /// -#if !NETSTANDARD1_6 +#if !NETSTANDARD1_6 && !NET [ReliabilityContract(Consistency.WillNotCorruptState, Cer.Success)] #endif protected override bool ReleaseHandle() diff --git a/source/icu.net/IcuWrapper.cs b/source/icu.net/IcuWrapper.cs index 754b2cca..a6c744b4 100644 --- a/source/icu.net/IcuWrapper.cs +++ b/source/icu.net/IcuWrapper.cs @@ -2,9 +2,12 @@ // This software is licensed under the MIT license (http://opensource.org/licenses/MIT) using System; using System.IO; +using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using JetBrains.Annotations; +[assembly: InternalsVisibleTo("icu.net.tests, PublicKey=0024000004800000940000000602000000240000525341310004000001000100f1a7e4dc5dedd55e54dbf599e2d82cf883c691e8bf81d0a8a993e2be9b7510ce6e7c2be8645e3b66d898f4f481b77bfcc57dfcbce28d744c06c3555d36afffaee59b1237e683cd9ea704d4529c5f48a9007a6408d6da069f991e4324c4ae804b0a6bff550ebf3cff44172b8df4bcb45841cf6fe23a2a34720d0ae059fa99a1d2")] + namespace Icu { /// diff --git a/source/icu.net/NativeMethods/DllResolver.cs b/source/icu.net/NativeMethods/DllResolver.cs new file mode 100644 index 00000000..fe98765c --- /dev/null +++ b/source/icu.net/NativeMethods/DllResolver.cs @@ -0,0 +1,74 @@ +// Copyright (c) 2022 SIL International +// This software is licensed under the MIT License (http://opensource.org/licenses/MIT) + +#if NET +using System; +using System.IO; +using System.Linq; +using System.Reflection; +using System.Runtime.InteropServices; +using System.Xml.Linq; + +namespace Icu +{ + // Mono has the concept of DllMaps in the .dll.config file that allows to map a dll name to + // the platform specific implementation. + // .NET Core 3.1 and .NET (>= 5.0) don't support this, but offer a different mechanism which + // this class implements. + // https://github.com/dotnet/runtime/blob/main/docs/design/features/dllmap.md + internal class DllResolver + { + public DllResolver(Assembly assembly) + { + NativeLibrary.SetDllImportResolver(assembly, MapAndLoad); + } + + // https://github.com/dotnet/samples/blob/main/core/extensions/DllMapDemo/Map.cs + + // The callback: which loads the mapped library in place of the original + private static IntPtr MapAndLoad(string libraryName, Assembly assembly, DllImportSearchPath? dllImportSearchPath) + { + var mappedName = MapLibraryName(assembly.Location, libraryName); + return NativeLibrary.Load(mappedName, assembly, dllImportSearchPath); + } + + // Parse the assembly.config file, and map the old name to the new name of a library. + internal static string MapLibraryName(string assemblyLocation, string originalLibName) + { + var configFile = Path.Combine(Path.GetDirectoryName(assemblyLocation), + Path.GetFileName(assemblyLocation) + ".config"); + + if (!File.Exists(configFile)) + return originalLibName; + + var mappedLibName = originalLibName; + var root = XElement.Load(configFile); + var map = + (from el in root.Elements("dllmap") + where (string)el.Attribute("dll") == originalLibName && ConditionApplies((string)el.Attribute("os")) + select el).SingleOrDefault(); + return map != null ? map.Attribute("target").Value : originalLibName; + } + + internal static bool ConditionApplies(string condition) + { + // + // + + var negate = condition.StartsWith("!"); + if (negate) + condition = condition.Substring(1); + var retVal = negate; + foreach (var platform in condition.Split(',')) + { + var osPlatform = OSPlatform.Create(platform.Trim()); + if (negate) + retVal &= !RuntimeInformation.IsOSPlatform(osPlatform); + else + retVal |= RuntimeInformation.IsOSPlatform(osPlatform); + } + return retVal; + } + } +} +#endif diff --git a/source/icu.net/NativeMethods/NativeMethods.cs b/source/icu.net/NativeMethods/NativeMethods.cs index f4b23b08..494c1300 100644 --- a/source/icu.net/NativeMethods/NativeMethods.cs +++ b/source/icu.net/NativeMethods/NativeMethods.cs @@ -19,6 +19,11 @@ internal static partial class NativeMethods { private static readonly object _lock = new object(); +#if NET + private static readonly DllResolver _DllResolver = + new DllResolver(Assembly.GetExecutingAssembly()); +#endif + internal static int MinIcuVersion { get; private set; } = Wrapper.MinSupportedIcuVersion; internal static int MaxIcuVersion { get; private set; } = Wrapper.MaxSupportedIcuVersion; @@ -166,7 +171,11 @@ internal static string DirectoryOfThisAssembly #else var currentAssembly = typeof(NativeMethods).GetTypeInfo().Assembly; #endif +#if NET + var managedPath = currentAssembly.Location; +#else var managedPath = currentAssembly.CodeBase ?? currentAssembly.Location; +#endif var uri = new Uri(managedPath); var directoryName = Path.GetDirectoryName(uri.LocalPath); diff --git a/source/icu.net/SafeEnumeratorHandle.cs b/source/icu.net/SafeEnumeratorHandle.cs index c49eaad7..5522de4b 100644 --- a/source/icu.net/SafeEnumeratorHandle.cs +++ b/source/icu.net/SafeEnumeratorHandle.cs @@ -23,7 +23,7 @@ public SafeEnumeratorHandle() : base(IntPtr.Zero, true) /// failure, false. In this case, it generates a ReleaseHandleFailed Managed Debugging /// Assistant. /// -#if !NETSTANDARD1_6 +#if !NETSTANDARD1_6 && !NET [ReliabilityContract(Consistency.WillNotCorruptState, Cer.Success)] #endif protected override bool ReleaseHandle() diff --git a/source/icu.net/Transliterator.cs b/source/icu.net/Transliterator.cs index 9351bbbf..f0a56f65 100644 --- a/source/icu.net/Transliterator.cs +++ b/source/icu.net/Transliterator.cs @@ -32,7 +32,7 @@ protected override bool ReleaseHandle() } } - private SafeTransliteratorHandle _transliteratorHandle; + private readonly SafeTransliteratorHandle _transliteratorHandle; #region Static Methods ///