ソースを参照

Improved loading native libraries

Marcin Ziąbek 1 年間 前
コミット
60f7f548c2

+ 5 - 0
Source/QuestPDF/Drawing/Exceptions/InitializationException.cs

@@ -4,6 +4,11 @@ namespace QuestPDF.Drawing.Exceptions
 {
 {
     public class InitializationException : Exception
     public class InitializationException : Exception
     {
     {
+        internal InitializationException(string message) : base(message)
+        {
+            
+        }
+        
         internal InitializationException(string message, Exception inner) : base(message, inner)
         internal InitializationException(string message, Exception inner) : base(message, inner)
         {
         {
             
             

+ 1 - 1
Source/QuestPDF/QuestPDF.csproj

@@ -3,7 +3,7 @@
         <Authors>MarcinZiabek</Authors>
         <Authors>MarcinZiabek</Authors>
         <Company>CodeFlint</Company>
         <Company>CodeFlint</Company>
         <PackageId>QuestPDF</PackageId>
         <PackageId>QuestPDF</PackageId>
-        <Version>2024.3.0-internal83</Version>
+        <Version>2024.3.0-internal91</Version>
         <PackageDescription>QuestPDF is an open-source, modern and battle-tested library that can help you with generating PDF documents by offering friendly, discoverable and predictable C# fluent API. Easily generate PDF reports, invoices, exports, etc.</PackageDescription>
         <PackageDescription>QuestPDF is an open-source, modern and battle-tested library that can help you with generating PDF documents by offering friendly, discoverable and predictable C# fluent API. Easily generate PDF reports, invoices, exports, etc.</PackageDescription>
         <PackageReleaseNotes>$([System.IO.File]::ReadAllText("$(MSBuildProjectDirectory)/Resources/ReleaseNotes.txt"))</PackageReleaseNotes>
         <PackageReleaseNotes>$([System.IO.File]::ReadAllText("$(MSBuildProjectDirectory)/Resources/ReleaseNotes.txt"))</PackageReleaseNotes>
         <LangVersion>10</LangVersion>
         <LangVersion>10</LangVersion>

+ 3 - 3
Source/QuestPDF/QuestPDF.targets

@@ -1,17 +1,17 @@
 <?xml version="1.0" encoding="utf-8"?>
 <?xml version="1.0" encoding="utf-8"?>
 <Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
 <Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
     <ItemGroup>
     <ItemGroup>
-        <None Include="$(MSBuildThisFileDirectory)\..\runtimes\win-x64\native\QuestPdfSkia.dll">
+        <None Include="$(MSBuildThisFileDirectory)\..\runtimes\win-x64\native\QuestPdfSkia.dll" Condition="$(TargetFramework.StartsWith('net4'))">
             <Link>QuestPdfSkia.dll</Link>
             <Link>QuestPdfSkia.dll</Link>
             <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
             <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
         </None>
         </None>
 
 
-        <None Include="$(MSBuildThisFileDirectory)\..\runtimes\win-x64\native\icudtl.dat">
+        <None Include="$(MSBuildThisFileDirectory)\..\runtimes\win-x64\native\icudtl.dat" Condition="$(TargetFramework.StartsWith('net4'))">
             <Link>icudtl.dat</Link>
             <Link>icudtl.dat</Link>
             <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
             <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
         </None>
         </None>
 
 
-        <None Include="$(MSBuildThisFileDirectory)\..\runtimes\any\native\LatoFont\*.*">
+        <None Include="$(MSBuildThisFileDirectory)\..\runtimes\any\native\LatoFont\*.*" Condition="$(TargetFramework.StartsWith('net4'))">
             <Link>LatoFont\%(Filename)%(Extension)</Link>
             <Link>LatoFont\%(Filename)%(Extension)</Link>
             <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
             <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
         </None>
         </None>

+ 18 - 3
Source/QuestPDF/Skia/SkNativeDependencyCompatibilityChecker.cs

@@ -1,5 +1,6 @@
 using System;
 using System;
 using System.Runtime.InteropServices;
 using System.Runtime.InteropServices;
+using QuestPDF.Drawing.Exceptions;
 
 
 namespace QuestPDF.Skia;
 namespace QuestPDF.Skia;
 
 
@@ -12,17 +13,31 @@ public static class SkNativeDependencyCompatibilityChecker
         if (IsCompatibilityChecked)
         if (IsCompatibilityChecked)
             return;
             return;
             
             
+        // test with dotnet-based mechanism where native files are provided
+        // in the "runtimes/{rid}/native" folder on Core, or by the targets file on .NET Framework
         var innerException = CheckIfExceptionIsThrownWhenLoadingNativeDependencies();
         var innerException = CheckIfExceptionIsThrownWhenLoadingNativeDependencies();
 
 
+        if (innerException == null)
+        {
+            IsCompatibilityChecked = true;
+            return;
+        }
+        
+        if (SkNativeDependencyProvider.IsCurrentPlatformSupported())
+            throw new InitializationException($"Your runtime is currently not supported by QuestPDF.");
+        
+        // detect platform, copy appropriate native files and test compatibility again
+        SkNativeDependencyProvider.EnsureNativeFileAvailability();
+        
+        innerException = CheckIfExceptionIsThrownWhenLoadingNativeDependencies();
+
         if (innerException == null)
         if (innerException == null)
         {
         {
             IsCompatibilityChecked = true;
             IsCompatibilityChecked = true;
             return;
             return;
         }
         }
 
 
-        // TODO: improve error message
-        var initializationExceptionMessage =
-            $"The QuestPDF library has encountered an issue while loading one of its dependencies.";
+        var initializationExceptionMessage = $"The QuestPDF library has encountered an issue while loading one of its dependencies.";
         
         
         throw new Exception(initializationExceptionMessage, innerException);
         throw new Exception(initializationExceptionMessage, innerException);
     }
     }

+ 80 - 0
Source/QuestPDF/Skia/SkNativeDependencyProvider.cs

@@ -0,0 +1,80 @@
+using System;
+using System.IO;
+using System.Runtime.InteropServices;
+using QuestPDF.Drawing.Exceptions;
+
+namespace QuestPDF.Skia;
+
+internal static class SkNativeDependencyProvider
+{
+    public static void EnsureNativeFileAvailability()
+    {
+        var nativeFiles = Directory.GetFiles(GetNativeFileSourcePath());
+
+        foreach (var nativeFileSourcePath in nativeFiles)
+        {
+            var nativeFileName = Path.GetFileName(nativeFileSourcePath);
+            var runtimePath = GetNativeFileRuntimePath(nativeFileName);
+
+            CopyFileIfNewer(nativeFileSourcePath, runtimePath);
+        }
+    }
+    
+    public static bool IsCurrentPlatformSupported()
+    {
+        try
+        {
+            EnsureNativeFileAvailability();
+            return true;
+        }
+        catch
+        {
+            return false;
+        }
+    }
+    
+    static string GetNativeFileSourcePath()
+    {
+        var applicationDirectory = AppDomain.CurrentDomain.BaseDirectory;
+        var platform = GetRuntimePlatform();
+        return Path.Combine(applicationDirectory, "runtimes", platform, "native");
+    }
+        
+    static string GetRuntimePlatform()
+    {
+        if (RuntimeInformation.ProcessArchitecture == Architecture.X64)
+        {
+            if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
+                return "win-x64";
+                
+            if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux))
+                return "linux-x64";
+                
+            if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX))
+                return "osx-x64";
+        }
+            
+        if (RuntimeInformation.ProcessArchitecture == Architecture.Arm64)
+        {
+            if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX))
+                return "osx-arm64";
+        }
+
+        throw new InitializationException("Your runtime is currently not supported by QuestPDF");
+    }
+        
+    static string GetNativeFileRuntimePath(string fileName)
+    {
+        var applicationDirectory = AppDomain.CurrentDomain.BaseDirectory;
+        return Path.Combine(applicationDirectory, fileName);
+    }
+
+    static void CopyFileIfNewer(string sourcePath, string targetPath)
+    {
+        if (!File.Exists(sourcePath))
+            throw new FileNotFoundException($"Source file not found: {sourcePath}");
+
+        if (!File.Exists(targetPath) || File.GetLastWriteTime(sourcePath) > File.GetLastWriteTime(targetPath))
+            File.Copy(sourcePath, targetPath, true);
+    }
+}