Skip to content

Commit aba183c

Browse files
hueifengclaude
andcommitted
feat: add macOS arm64 static wkhtmltopdf dylib and compatibility fixes
- Build static libwkhtmltox.dylib (53MB) for macOS arm64 with Qt 5.15 and QtWebKit statically linked, zero external dependencies - Fix netstandard2.0 compatibility: init→set properties, RuntimeInformation.RuntimeIdentifier fallback, string.Contains IndexOf workaround - Add arm64→x64 native library path fallback in PdfNativeLibraryBootstrapper - Update CI workflow to install wkhtmltopdf on Linux and macOS - Replace old dynamic libwkhtmltox-next.0.dylib with static libwkhtmltox.dylib Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
1 parent 7aabcb6 commit aba183c

6 files changed

Lines changed: 62 additions & 21 deletions

File tree

.github/workflows/dotnetcore.yml

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,11 @@ jobs:
2929
3030
- name: Install Linux dependencies
3131
if: runner.os == 'Linux'
32-
run: sudo apt-get update && sudo apt-get install -y libgdiplus libc6-dev libjpeg62
32+
run: sudo apt-get update && sudo apt-get install -y libgdiplus libc6-dev libjpeg62 wkhtmltopdf fontconfig libxrender1 xfonts-75dpi
33+
34+
- name: Install macOS dependencies
35+
if: runner.os == 'macOS'
36+
run: brew install wkhtmltopdf
3337

3438
- name: Restore
3539
run: dotnet restore Magicodes.IE.sln

src/Magicodes.ExporterAndImporter.Pdf/PdfEnvironmentInfo.cs

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -23,41 +23,41 @@ public sealed class PdfEnvironmentInfo
2323
/// <summary>
2424
/// 当前平台标识(如 "osx-arm64", "linux-x64", "win-x64")。
2525
/// </summary>
26-
public string Platform { get; init; }
26+
public string Platform { get; set; }
2727

2828
/// <summary>
2929
/// wkhtmltopdf native 库是否可用。
3030
/// 为 true 表示 PDF 导出功能可以正常工作。
3131
/// </summary>
32-
public bool IsAvailable { get; init; }
32+
public bool IsAvailable { get; set; }
3333

3434
/// <summary>
3535
/// 找到的 native 库文件路径。
3636
/// 可能来自 runtimes/ 目录或系统路径。
3737
/// </summary>
38-
public string NativeLibraryPath { get; init; }
38+
public string NativeLibraryPath { get; set; }
3939

4040
/// <summary>
4141
/// native 库文件是否存在(不论是否能加载)。
4242
/// 文件存在但加载失败通常意味着缺少系统依赖(Qt、fontconfig 等)。
4343
/// </summary>
44-
public bool NativeLibraryFileExists { get; init; }
44+
public bool NativeLibraryFileExists { get; set; }
4545

4646
/// <summary>
4747
/// native 库是否能成功加载(通过 NativeLibrary.TryLoad 探测)。
4848
/// </summary>
49-
public bool NativeLibraryLoadable { get; init; }
49+
public bool NativeLibraryLoadable { get; set; }
5050

5151
/// <summary>
5252
/// 加载失败时的详细错误信息。
5353
/// </summary>
54-
public string ErrorDetail { get; init; }
54+
public string ErrorDetail { get; set; }
5555

5656
/// <summary>
5757
/// 针对当前平台的安装建议。
5858
/// 包含具体的安装命令。
5959
/// </summary>
60-
public string InstallSuggestion { get; init; }
60+
public string InstallSuggestion { get; set; }
6161

6262
/// <summary>
6363
/// 检查当前环境并返回诊断信息。不会抛出异常。

src/Magicodes.ExporterAndImporter.Pdf/PdfNativeLibraryBootstrapper.cs

Lines changed: 46 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
using System;
2+
using System.Collections.Generic;
23
using System.IO;
34
using System.Runtime.InteropServices;
45

@@ -24,7 +25,7 @@ internal static class PdfNativeLibraryBootstrapper
2425

2526
private static PdfEnvironmentInfo CheckEnvironmentCore()
2627
{
27-
var rid = RuntimeInformation.RuntimeIdentifier;
28+
var rid = GetCurrentRuntimeIdentifier();
2829
var nativePath = FindNativeLibraryPath(rid);
2930
var loadable = TryProbeNativeLibrary(nativePath, out var error);
3031

@@ -40,26 +41,58 @@ private static PdfEnvironmentInfo CheckEnvironmentCore()
4041
};
4142
}
4243

44+
/// <summary>
45+
/// 获取当前运行时标识符(跨框架兼容)。
46+
/// </summary>
47+
private static string GetCurrentRuntimeIdentifier()
48+
{
49+
#if NET5_0_OR_GREATER
50+
return RuntimeInformation.RuntimeIdentifier;
51+
#else
52+
var arch = RuntimeInformation.OSArchitecture.ToString().ToLowerInvariant();
53+
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
54+
return "win-" + arch;
55+
if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX))
56+
return "osx-" + arch;
57+
return "linux-" + arch;
58+
#endif
59+
}
60+
4361
/// <summary>
4462
/// 查找 native 库文件路径。
4563
/// 搜索 runtimes/{rid}/native/ 和应用根目录。
64+
/// 在 arm64 平台上如果找不到,会自动 fallback 到 x64 版本(通过 Rosetta 2 运行)。
4665
/// </summary>
4766
private static string FindNativeLibraryPath(string rid)
4867
{
49-
var dirs = new[]
68+
var baseDir = AppContext.BaseDirectory;
69+
var candidateRids = new List<string> { rid };
70+
71+
// arm64 平台 fallback:wkhtmltopdf 等 native 库通常没有 arm64 版本,
72+
// x64 版本可通过 Rosetta 2 (macOS) 或兼容层 (Linux) 运行。
73+
if (rid.EndsWith("-arm64", StringComparison.OrdinalIgnoreCase))
5074
{
51-
Path.Combine(AppContext.BaseDirectory, "runtimes", rid, "native"),
52-
AppContext.BaseDirectory
53-
};
75+
var x64Rid = rid.Substring(0, rid.Length - "arm64".Length) + "x64";
76+
candidateRids.Add(x64Rid);
77+
}
5478

55-
var names = GetNativeLibraryNames(rid);
56-
foreach (var dir in dirs)
79+
foreach (var candidateRid in candidateRids)
5780
{
58-
if (!Directory.Exists(dir)) continue;
59-
foreach (var name in names)
81+
var dirs = new[]
82+
{
83+
Path.Combine(baseDir, "runtimes", candidateRid, "native"),
84+
baseDir
85+
};
86+
87+
var names = GetNativeLibraryNames(candidateRid);
88+
foreach (var dir in dirs)
6089
{
61-
var path = Path.Combine(dir, name);
62-
if (File.Exists(path)) return path;
90+
if (!Directory.Exists(dir)) continue;
91+
foreach (var name in names)
92+
{
93+
var path = Path.Combine(dir, name);
94+
if (File.Exists(path)) return path;
95+
}
6396
}
6497
}
6598
return null;
@@ -70,7 +103,7 @@ private static string[] GetNativeLibraryNames(string rid)
70103
if (rid.StartsWith("win", StringComparison.OrdinalIgnoreCase))
71104
return new[] { "wkhtmltox.dll" };
72105
if (rid.StartsWith("osx", StringComparison.OrdinalIgnoreCase))
73-
return new[] { "libwkhtmltox-next.0.dylib", "libwkhtmltox.dylib", "libwkhtmltox-next.dylib" };
106+
return new[] { "libwkhtmltox.dylib" };
74107
return new[] { "libwkhtmltox.so" };
75108
}
76109

@@ -108,7 +141,7 @@ private static string GetInstallSuggestion(string rid)
108141
return "brew install wkhtmltopdf # macOS";
109142

110143
// Alpine Linux (musl libc) - Docker 最常用的基础镜像之一
111-
if (rid.Contains("musl", StringComparison.OrdinalIgnoreCase))
144+
if (rid.IndexOf("musl", StringComparison.OrdinalIgnoreCase) >= 0)
112145
return "apk add --no-cache wkhtmltopdf fontconfig ttf-freefont ttf-dejavu # Alpine\n" +
113146
"Note: Alpine uses musl libc. If using pre-built glibc binaries, also run: apk add --no-cache libc6-compat";
114147

Binary file not shown.
Binary file not shown.

src/Magicodes.ExporterAndImporter.Tests/PdfNativeLibraryBootstrapper_Tests.cs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,11 @@ public void CheckEnvironment_ToString_ContainsPlatformAndDiagnostics()
4949
public void CheckEnvironment_Platform_MatchesRuntimeIdentifier()
5050
{
5151
var info = PdfEnvironmentInfo.Check();
52+
#if NET5_0_OR_GREATER
5253
info.Platform.ShouldBe(RuntimeInformation.RuntimeIdentifier);
54+
#else
55+
info.Platform.ShouldNotBeNullOrWhiteSpace();
56+
#endif
5357
}
5458
}
5559
}

0 commit comments

Comments
 (0)