From 6659385caad85cdd785d093431bbe89eba958336 Mon Sep 17 00:00:00 2001 From: KakaruHayate Date: Tue, 28 Jul 2026 23:43:06 +0800 Subject: [PATCH 1/6] feat: add macOS and Linux CPU support --- .gitignore | 1 + DiffSingerForTuneLab.csproj | 31 +++-- DiffSingerModels.cs | 10 +- DiffSingerVoiceEngine.cs | 18 ++- DirectMlNative.cs | 10 +- MLRuntime/MLRuntime.csproj | 14 ++- MLRuntime/OnnxNativeResolver.cs | 11 +- OpenUtauFacade/OpenUtau.Core.csproj | 10 +- RuntimeClient.cs | 4 +- RuntimeHost.cs | 4 +- RuntimePlatform.cs | 50 ++++++++ manifest.json | 2 +- packaging/README.md | 12 +- packaging/README.zh-CN.md | 12 +- .../DiffSinger.Tests/DiffSinger.Tests.csproj | 1 + .../DiffSinger.Tests/RuntimePlatformTests.cs | 44 +++++++ .../DiffSingerSmokeTest.csproj | 11 +- tools/pack-tlx.ps1 | 119 +++++++++++++----- 18 files changed, 265 insertions(+), 99 deletions(-) create mode 100644 RuntimePlatform.cs create mode 100644 tests/DiffSinger.Tests/RuntimePlatformTests.cs diff --git a/.gitignore b/.gitignore index 04d0096..3175ab3 100644 --- a/.gitignore +++ b/.gitignore @@ -17,3 +17,4 @@ obj/ # 打包产物 *.tlx /build/ +/artifacts/ diff --git a/DiffSingerForTuneLab.csproj b/DiffSingerForTuneLab.csproj index f28eb31..89d2704 100644 --- a/DiffSingerForTuneLab.csproj +++ b/DiffSingerForTuneLab.csproj @@ -13,6 +13,13 @@ true + + $(RuntimeIdentifier) + win-x64 + osx-arm64 + linux-x64 + $([System.String]::Copy('$(DiffSingerRuntimeIdentifier)').StartsWith('win-')) - + + @@ -82,21 +88,14 @@ - + - - - + + + + diff --git a/DiffSingerModels.cs b/DiffSingerModels.cs index a9361aa..2e7f4ae 100644 --- a/DiffSingerModels.cs +++ b/DiffSingerModels.cs @@ -35,16 +35,16 @@ public sealed class DiffSingerModelCache : IDisposable public DiffSingerModelCache(string provider, string runtimeMode, ILogger logger) { - mProvider = provider; + mProvider = RuntimePlatform.NormalizeProvider(provider); mLogger = logger; if (Environment.GetEnvironmentVariable("DIFFSINGER_RUNTIME_LOOPBACK") == "1") { - mRuntimeClient = new RuntimeClient(provider, p => new LoopbackTransport(new RuntimeHost(p)), canRespawn: false); + mRuntimeClient = new RuntimeClient(mProvider, p => new LoopbackTransport(new RuntimeHost(p)), canRespawn: false); mLogger.Info("DiffSinger:MLRuntime loopback 模式(dev)"); } else if (runtimeMode != "inprocess") // 默认 subprocess { - mRuntimeClient = TryCreateSubprocessClient(provider); + mRuntimeClient = TryCreateSubprocessClient(mProvider); } } @@ -53,7 +53,7 @@ public DiffSingerModelCache(string provider, string runtimeMode, ILogger logger) RuntimeClient? TryCreateSubprocessClient(string provider) { var dir = Path.GetDirectoryName(typeof(DiffSingerModelCache).Assembly.Location)!; - var exe = Path.Combine(dir, "mlruntime", "MLRuntime.exe"); + var exe = Path.Combine(dir, "mlruntime", RuntimePlatform.RuntimeExecutableName); Action exeLog = line => mLogger.Info($"[MLRuntime] {line}"); // 子进程 stdout/stderr 转发 Func factory = p => new PipeTransport(exe, p, exeLog); try @@ -170,7 +170,7 @@ IModelSession LoadSession(string modelPath) mLogger.Info($"DiffSinger:加载 {fileName} · MLRuntime({mProvider})"); return remote; } - if (mProvider == "cpu") + if (!RuntimePlatform.IsDirectML(mProvider)) { // 图优化用 onnxruntime 默认(ORT_ENABLE_ALL):1.20.1 时代的 BASIC 封顶已随 1.23 升级取消,见 RuntimeHost.LoadSession。 var cpu = new InferenceSession(modelPath); diff --git a/DiffSingerVoiceEngine.cs b/DiffSingerVoiceEngine.cs index e0f3096..00333dd 100644 --- a/DiffSingerVoiceEngine.cs +++ b/DiffSingerVoiceEngine.cs @@ -141,7 +141,8 @@ VoicebankConfig ConfigForRoot(string rootPath) // 模型缓存按当前执行设备设置懒建;provider 变更则弃旧建新(旧缓存 Dispose 释放原生会话)。 DiffSingerModelCache EnsureModelCache() { - var provider = mSettings.GetString(KeyExecutionProvider, "directml"); + var provider = RuntimePlatform.NormalizeProvider( + mSettings.GetString(KeyExecutionProvider, RuntimePlatform.DefaultProvider)); var runtimeMode = mSettings.GetString(KeyRuntimeMode, "subprocess"); if (mModelCache == null || mProviderInUse != provider || mRuntimeModeInUse != runtimeMode) { @@ -169,11 +170,7 @@ public ObjectConfig GetSettingsConfig(IExtensionSettingsContext context) }, { (KeyExecutionProvider, L.Tr("Execution device")), - ComboBoxConfig.Create(new List - { - new(PropertyValue.Create("directml"), L.Tr("GPU (DirectML)")), - new(PropertyValue.Create("cpu"), L.Tr("CPU")), - }) + ComboBoxConfig.Create(ExecutionProviderItems()) }, { (KeyRuntimeMode, L.Tr("Inference mode")), @@ -199,6 +196,15 @@ public ObjectConfig GetSettingsConfig(IExtensionSettingsContext context) return ObjectConfig.Create(properties); } + static List ExecutionProviderItems() + { + var items = new List(); + if (RuntimePlatform.SupportsDirectML) + items.Add(new(PropertyValue.Create(RuntimePlatform.DirectMlProvider), L.Tr("GPU (DirectML)"))); + items.Add(new(PropertyValue.Create(RuntimePlatform.CpuProvider), L.Tr("CPU"))); + return items; + } + // 目录列表(变长):每行一个路径 TextBox、+ 追加空行。行数按当前已存值算—— // 缺席(从未配置)= 0 行、不 seed(默认目录本就隐式生效,无需列在此)。 static ListConfig DirListConfig(PropertyObject settings, string key) diff --git a/DirectMlNative.cs b/DirectMlNative.cs index 0293d43..7e7f8c7 100644 --- a/DirectMlNative.cs +++ b/DirectMlNative.cs @@ -16,15 +16,11 @@ internal static class DirectMlNative { public static void Preload(string pluginRootDir) { + if (!OperatingSystem.IsWindows()) + return; try { - string rid = RuntimeInformation.ProcessArchitecture switch - { - Architecture.X86 => "win-x86", - Architecture.Arm64 => "win-arm64", - _ => "win-x64", - }; - var dml = Path.Combine(pluginRootDir, "runtimes", rid, "native", "DirectML.dll"); + var dml = Path.Combine(pluginRootDir, "runtimes", RuntimePlatform.RuntimeIdentifier, "native", "DirectML.dll"); if (File.Exists(dml)) NativeLibrary.TryLoad(dml, out _); } diff --git a/MLRuntime/MLRuntime.csproj b/MLRuntime/MLRuntime.csproj index ddff41e..5906c3f 100644 --- a/MLRuntime/MLRuntime.csproj +++ b/MLRuntime/MLRuntime.csproj @@ -9,18 +9,24 @@ enable MLRuntime DiffSingerForTuneLab - + true - x64 + $(RuntimeIdentifier) + win-x64 + osx-arm64 + linux-x64 + $([System.String]::Copy('$(DiffSingerRuntimeIdentifier)').StartsWith('win-')) - - + + + + diff --git a/MLRuntime/OnnxNativeResolver.cs b/MLRuntime/OnnxNativeResolver.cs index ff1816e..3b4ce72 100644 --- a/MLRuntime/OnnxNativeResolver.cs +++ b/MLRuntime/OnnxNativeResolver.cs @@ -18,20 +18,15 @@ public static void Register() if (pluginDir == null) return; - string rid = RuntimeInformation.ProcessArchitecture switch - { - Architecture.X86 => "win-x86", - Architecture.Arm64 => "win-arm64", - _ => "win-x64", - }; - var candidate = Path.Combine(pluginDir, "runtimes", rid, "native", "onnxruntime.dll"); + var candidate = Path.Combine( + pluginDir, "runtimes", RuntimePlatform.RuntimeIdentifier, "native", RuntimePlatform.OnnxRuntimeLibraryName); NativeLibrary.SetDllImportResolver(typeof(Microsoft.ML.OnnxRuntime.SessionOptions).Assembly, (name, _, _) => name == "onnxruntime" && File.Exists(candidate) && NativeLibrary.TryLoad(candidate, out var handle) ? handle : IntPtr.Zero); // 回退默认解析(自带 runtimes/ 仍在时可载) - // 预载随包 DirectML.dll(否则 DML EP delay-load 会走到 System32 的旧版而失败,见 DirectMlNative 说明)。 + // 仅 Windows 的 DirectML 构建需要预载;CPU-only 的 macOS/Linux 上该方法直接返回。 DirectMlNative.Preload(pluginDir); } } diff --git a/OpenUtauFacade/OpenUtau.Core.csproj b/OpenUtauFacade/OpenUtau.Core.csproj index 9185589..8366e0e 100644 --- a/OpenUtauFacade/OpenUtau.Core.csproj +++ b/OpenUtauFacade/OpenUtau.Core.csproj @@ -9,6 +9,11 @@ net8.0 disable + $(RuntimeIdentifier) + win-x64 + osx-arm64 + linux-x64 + $([System.String]::Copy('$(DiffSingerRuntimeIdentifier)').StartsWith('win-')) disable OpenUtau @@ -18,8 +23,9 @@ - - + + + diff --git a/RuntimeClient.cs b/RuntimeClient.cs index b12e281..b979e71 100644 --- a/RuntimeClient.cs +++ b/RuntimeClient.cs @@ -27,7 +27,7 @@ internal sealed class RuntimeClient : IDisposable public RuntimeClient(string provider, Func transportFactory, bool canRespawn, Action? log = null, IRuntimeTransport? initialTransport = null) { - mProvider = provider; + mProvider = RuntimePlatform.NormalizeProvider(provider); mFactory = transportFactory; mCanRespawn = canRespawn; mLog = log; @@ -49,7 +49,7 @@ public IReadOnlyDictionary Load(string path) { return LoadInner(path); } - catch (RuntimeHostException ex) when (mCanRespawn && mProvider != "cpu") + catch (RuntimeHostException ex) when (mCanRespawn && RuntimePlatform.IsDirectML(mProvider)) { // DML 加载失败(host 报错、exe 存活、非崩溃):不自动切 CPU,抛可读错误让用户手动改执行设备。 throw new InvalidOperationException( diff --git a/RuntimeHost.cs b/RuntimeHost.cs index c25f0a4..38f7b7f 100644 --- a/RuntimeHost.cs +++ b/RuntimeHost.cs @@ -18,7 +18,7 @@ internal sealed class RuntimeHost : IDisposable readonly Dictionary mSessions = new(); int mNextId = 1; - public RuntimeHost(string provider) => mProvider = provider; + public RuntimeHost(string provider) => mProvider = RuntimePlatform.NormalizeProvider(provider); // 处理一个请求帧、返回一个响应帧。任何异常 → Error 响应(携 message)。 public byte[] Handle(byte[] request) @@ -104,7 +104,7 @@ InferenceSession LoadSession(string modelPath) // 原生崩溃(AccessViolation,EXTENDED/ALL 必崩),故当时封顶 BASIC。1.23.0 上四档优化级别实测全绿 // (声学/linguistic/dur/pitch/variance/vocoder 全模型 × ORT_ENABLE_ALL),已松回 onnxruntime 默认。 // 降级 onnxruntime 或换用新模型若再现 Init 期 AccessViolation,先怀疑此处。 - if (mProvider != "cpu") + if (RuntimePlatform.IsDirectML(mProvider)) options.AppendExecutionProvider_DML(0); return new InferenceSession(modelPath, options); } diff --git a/RuntimePlatform.cs b/RuntimePlatform.cs new file mode 100644 index 0000000..af8b6b4 --- /dev/null +++ b/RuntimePlatform.cs @@ -0,0 +1,50 @@ +using System; +using System.Runtime.InteropServices; + +namespace DiffSingerForTuneLab; + +internal static class RuntimePlatform +{ + public const string CpuProvider = "cpu"; + public const string DirectMlProvider = "directml"; + + public static bool SupportsDirectML => OperatingSystem.IsWindows(); + + public static string DefaultProvider => SupportsDirectML ? DirectMlProvider : CpuProvider; + + public static string NormalizeProvider(string? provider) + => SupportsDirectML && string.Equals(provider, DirectMlProvider, StringComparison.OrdinalIgnoreCase) + ? DirectMlProvider + : CpuProvider; + + public static bool IsDirectML(string? provider) + => NormalizeProvider(provider) == DirectMlProvider; + + public static string RuntimeExecutableName => OperatingSystem.IsWindows() ? "MLRuntime.exe" : "MLRuntime"; + + public static string RuntimeIdentifier + { + get + { + var os = OperatingSystem.IsWindows() ? "win" + : OperatingSystem.IsMacOS() ? "osx" + : OperatingSystem.IsLinux() ? "linux" + : throw new PlatformNotSupportedException("DiffSinger 仅支持 Windows、macOS 和 Linux。"); + var architecture = RuntimeInformation.ProcessArchitecture switch + { + Architecture.X86 => "x86", + Architecture.X64 => "x64", + Architecture.Arm => "arm", + Architecture.Arm64 => "arm64", + _ => throw new PlatformNotSupportedException( + $"不支持的进程架构:{RuntimeInformation.ProcessArchitecture}"), + }; + return $"{os}-{architecture}"; + } + } + + public static string OnnxRuntimeLibraryName => OperatingSystem.IsWindows() ? "onnxruntime.dll" + : OperatingSystem.IsMacOS() ? "libonnxruntime.dylib" + : OperatingSystem.IsLinux() ? "libonnxruntime.so" + : throw new PlatformNotSupportedException("DiffSinger 仅支持 Windows、macOS 和 Linux。"); +} diff --git a/manifest.json b/manifest.json index 88469d7..abc0486 100644 --- a/manifest.json +++ b/manifest.json @@ -9,5 +9,5 @@ "engine": "DiffSinger", "classes": ["DiffSingerForTuneLab.DiffSingerVoiceEngine"], "assembly": "DiffSingerForTuneLab.dll", - "platforms": ["win"] + "platforms": ["win-x64", "osx-arm64", "linux-x64"] } diff --git a/packaging/README.md b/packaging/README.md index 904f412..3961295 100644 --- a/packaging/README.md +++ b/packaging/README.md @@ -2,7 +2,7 @@ A [DiffSinger](https://github.com/openvpi/DiffSinger)-based singing voice synthesis engine for TuneLab. It reads DiffSinger voicebanks in the **standard community format** — a model folder containing `dsconfig.yaml` + character metadata + predictor subdirectories — directly, with no conversion or repackaging. -> **Windows only.** Optional GPU acceleration (DirectML, works with most discrete/integrated GPUs); falls back to CPU when no GPU is available. +> Supported packages: **Windows x64**, **macOS Apple Silicon (arm64)**, and **Linux x64**. Windows offers DirectML GPU acceleration or CPU inference; macOS and Linux currently use the CPU execution provider only. --- @@ -11,10 +11,10 @@ A [DiffSinger](https://github.com/openvpi/DiffSinger)-based singing voice synthe The plugin **ships with no models** — you supply your own. Default scan directory: ``` -%APPDATA%\DiffSingerForTuneLab\Voices +/DiffSingerForTuneLab/Voices ``` -(i.e. `C:\Users\\AppData\Roaming\DiffSingerForTuneLab\Voices` — created automatically on first launch.) +This is the operating system's roaming application-data directory (for example `%APPDATA%` on Windows and `~/.config` on many Linux desktops). The plugin creates the directory on first launch. Drop the **whole model folder** in, e.g.: @@ -41,7 +41,7 @@ Open **Settings → Extensions → DiffSinger** and add folders to **"Voicebank A DiffSinger acoustic model outputs a mel-spectrogram; a **vocoder** is required to turn it into audio. Default vocoder directory: ``` -%APPDATA%\DiffSingerForTuneLab\Vocoders\\ +/DiffSingerForTuneLab/Vocoders// ├─ vocoder.yaml └─ .onnx ``` @@ -62,7 +62,7 @@ Vocoders can also live elsewhere: open **Settings → Extensions → DiffSinger* |---|---|---| | **Voicebank directories** | Extra model scan dirs (one per row) | empty (default dir only) | | **Vocoder directories** | Extra vocoder scan dirs (one per row) | empty (default dir only) | -| **Execution device** | `GPU (DirectML)` or `CPU`. GPU is noticeably faster; use CPU if the driver misbehaves or you have no GPU | GPU (DirectML) | +| **Execution device** | Windows: `GPU (DirectML)` or `CPU`; macOS/Linux: CPU only | DirectML on Windows; CPU elsewhere | | **Inference mode** | `Isolated process` runs ONNX in a separate process so a native crash can't take down TuneLab (auto-falls back to in-process if it can't start, e.g. blocked by antivirus); `In-process` runs inside TuneLab | Isolated process | | **Sampling steps** | Diffusion sampling steps. Higher = finer but slower; 20 is usually enough | 20 | | **Tensor cache** | Caches inference intermediates — repeated synthesis of the same segment is faster and reproducible | on | @@ -144,7 +144,7 @@ Morphs a sound **between phonemes over time** — e.g. an `a` gliding smoothly t - **Model missing from the singer list** → verify the folder has **both** `dsconfig.yaml` and `character.yaml` (or `.txt`); confirm it's under the default dir or a dir you added in settings; settings changes auto-rescan. - **No sound after synthesis** → see §2; usually a missing or misnamed vocoder. -- **Too slow** → set execution device to GPU (DirectML); or lower the sampling steps; keep the tensor cache on (repeated synthesis gets much faster). +- **Too slow** → on Windows, select GPU (DirectML); on every platform, lower sampling steps and keep the tensor cache enabled. Linux requires the system OpenMP runtime (commonly `libgomp.so.1`) used by ONNX Runtime. - **Reproducing a previous render** → keep the tensor cache on; identical input hits the cache and reproduces the result. --- diff --git a/packaging/README.zh-CN.md b/packaging/README.zh-CN.md index f7cd1c5..f51f0c3 100644 --- a/packaging/README.zh-CN.md +++ b/packaging/README.zh-CN.md @@ -2,7 +2,7 @@ 基于 [DiffSinger](https://github.com/openvpi/DiffSinger) 的 TuneLab 歌声合成引擎。直接读取**社区标准格式**的 DiffSinger 声库——即包含 `dsconfig.yaml` + 角色元数据 + 预测器子目录的模型文件夹——无需转换、无需重新打包。 -> 仅支持 **Windows**。可选 GPU 加速(DirectML,兼容大多数独显/核显),无 GPU 时自动可切 CPU。 +> 支持 **Windows x64**、**macOS Apple 芯片(arm64)** 和 **Linux x64**。Windows 可选 DirectML GPU 或 CPU 推理;macOS 与 Linux 当前仅支持 CPU EP。 --- @@ -11,10 +11,10 @@ 插件**不自带任何模型**,需要你自己放入。默认扫描目录: ``` -%APPDATA%\DiffSingerForTuneLab\Voices +<应用数据目录>/DiffSingerForTuneLab/Voices ``` -(即 `C:\Users\<你的用户名>\AppData\Roaming\DiffSingerForTuneLab\Voices`,插件首次启动会自动创建这个空目录。) +这里的应用数据目录由系统决定(例如 Windows 的 `%APPDATA%`,不少 Linux 桌面环境为 `~/.config`);插件首次启动会自动创建目录。 把**模型文件夹整个**放进去即可,例如: @@ -41,7 +41,7 @@ Voices\ DiffSinger 声学模型输出的是梅尔频谱,需要**声码器**才能还原成声音。默认声码器目录: ``` -%APPDATA%\DiffSingerForTuneLab\Vocoders\<声码器名>\ +<应用数据目录>/DiffSingerForTuneLab/Vocoders/<声码器名>/ ├─ vocoder.yaml └─ <模型>.onnx ``` @@ -62,7 +62,7 @@ DiffSinger 声学模型输出的是梅尔频谱,需要**声码器**才能还 |---|---|---| | **声库目录** | 追加的模型扫描目录(逐行添加) | 空(仅默认目录) | | **声码器目录** | 追加的声码器扫描目录(逐行添加) | 空(仅默认目录) | -| **执行设备** | `GPU (DirectML)` 或 `CPU`。GPU 明显更快;驱动异常/无独显时改 CPU | GPU (DirectML) | +| **执行设备** | Windows:`GPU (DirectML)` 或 `CPU`;macOS/Linux:仅 CPU | Windows 默认 DirectML,其余平台默认 CPU | | **推理模式** | `隔离进程`在独立子进程跑 ONNX,原生崩溃不会拖垮 TuneLab(子进程起不来时——如被杀软拦截——自动退回进程内);`进程内`直接在 TuneLab 内跑 | 隔离进程 | | **采样步数** | 扩散采样步数。越大越精细也越慢,通常 20 足够 | 20 | | **张量缓存** | 缓存推理中间结果,重复合成同段更快、且结果可复现 | 开 | @@ -144,7 +144,7 @@ voices: # 可选:出现即白名单;缺省=整模型 - **模型没出现在歌手列表里** → 检查目录里是否**同时**有 `dsconfig.yaml` 和 `character.yaml`(或 `.txt`);确认放对了目录(默认目录或设置里追加的目录);改完设置会自动重扫。 - **合成没有声音** → 见 §2,多半是声码器缺失或名字对不上。 -- **太慢** → 执行设备设为 GPU (DirectML);或调低采样步数;保持张量缓存开启(重复合成会显著变快)。 +- **太慢** → Windows 可选 GPU (DirectML);各平台都可调低采样步数并保持张量缓存开启。Linux 还需要 ONNX Runtime 使用的系统 OpenMP 运行库(常见包提供 `libgomp.so.1`)。 - **想复用某次合成结果** → 保持张量缓存开启;同样的输入会命中缓存、结果可复现。 --- diff --git a/tests/DiffSinger.Tests/DiffSinger.Tests.csproj b/tests/DiffSinger.Tests/DiffSinger.Tests.csproj index 1d31512..b713276 100644 --- a/tests/DiffSinger.Tests/DiffSinger.Tests.csproj +++ b/tests/DiffSinger.Tests/DiffSinger.Tests.csproj @@ -20,6 +20,7 @@ + diff --git a/tests/DiffSinger.Tests/RuntimePlatformTests.cs b/tests/DiffSinger.Tests/RuntimePlatformTests.cs new file mode 100644 index 0000000..9337452 --- /dev/null +++ b/tests/DiffSinger.Tests/RuntimePlatformTests.cs @@ -0,0 +1,44 @@ +using DiffSingerForTuneLab; +using Xunit; + +namespace DiffSinger.Tests; + +public sealed class RuntimePlatformTests +{ + [Fact] + public void NormalizeProvider_AlwaysAcceptsCpu() + => Assert.Equal(RuntimePlatform.CpuProvider, RuntimePlatform.NormalizeProvider("cpu")); + + [Fact] + public void NormalizeProvider_RejectsUnknownProvider() + => Assert.Equal(RuntimePlatform.CpuProvider, RuntimePlatform.NormalizeProvider("cuda")); + + [Fact] + public void NormalizeProvider_OnlyAllowsDirectMlOnWindows() + { + var expected = OperatingSystem.IsWindows() + ? RuntimePlatform.DirectMlProvider + : RuntimePlatform.CpuProvider; + Assert.Equal(expected, RuntimePlatform.NormalizeProvider("directml")); + } + + [Fact] + public void RuntimeNamesMatchCurrentOperatingSystem() + { + if (OperatingSystem.IsWindows()) + { + Assert.Equal("MLRuntime.exe", RuntimePlatform.RuntimeExecutableName); + Assert.Equal("onnxruntime.dll", RuntimePlatform.OnnxRuntimeLibraryName); + } + else if (OperatingSystem.IsMacOS()) + { + Assert.Equal("MLRuntime", RuntimePlatform.RuntimeExecutableName); + Assert.Equal("libonnxruntime.dylib", RuntimePlatform.OnnxRuntimeLibraryName); + } + else if (OperatingSystem.IsLinux()) + { + Assert.Equal("MLRuntime", RuntimePlatform.RuntimeExecutableName); + Assert.Equal("libonnxruntime.so", RuntimePlatform.OnnxRuntimeLibraryName); + } + } +} diff --git a/tools/DiffSingerSmokeTest/DiffSingerSmokeTest.csproj b/tools/DiffSingerSmokeTest/DiffSingerSmokeTest.csproj index 6c2ce3d..c6de3e4 100644 --- a/tools/DiffSingerSmokeTest/DiffSingerSmokeTest.csproj +++ b/tools/DiffSingerSmokeTest/DiffSingerSmokeTest.csproj @@ -9,17 +9,24 @@ enable DiffSingerForTuneLab.SmokeTest DiffSingerSmokeTest + $(RuntimeIdentifier) + win-x64 + osx-arm64 + linux-x64 + $([System.String]::Copy('$(DiffSingerRuntimeIdentifier)').StartsWith('win-')) - - + + + + diff --git a/tools/pack-tlx.ps1 b/tools/pack-tlx.ps1 index 8dbd05b..fa7e35f 100644 --- a/tools/pack-tlx.ps1 +++ b/tools/pack-tlx.ps1 @@ -1,47 +1,102 @@ -# 打 .tlx 分发包:构建插件并把输出目录【内容】压成 zip(改名 .tlx)。 -# .tlx = zip,根目录直接含 manifest.json + dll + runtimes/ 子树(勿套外层文件夹)。 -# ONNX 原生库在 runtimes/win-x64/native/,靠整树递归打入、勿扁平化。 -# 只有 .tlx 文件能被 TuneLab 安装(拖文件夹不装,见 Editor.OnDrop / InstallExtensions)。 -# 用法: pwsh tools/pack-tlx.ps1 [-Configuration Release] -param([string]$Configuration = "Release") +# Build one RID-specific .tlx package. The package root contains manifest.json, managed assemblies, +# runtimes//native/, and the framework-dependent MLRuntime apphost under mlruntime/. +# Usage: pwsh tools/pack-tlx.ps1 [-Configuration Release] [-RuntimeIdentifier win-x64|osx-arm64|linux-x64] +param( + [string]$Configuration = "Release", + [ValidateSet("win-x64", "osx-arm64", "linux-x64")] + [string]$RuntimeIdentifier = "win-x64" +) $ErrorActionPreference = "Stop" Add-Type -AssemblyName System.IO.Compression.FileSystem $repo = Split-Path $PSScriptRoot -Parent -$source = Join-Path $repo "bin/$Configuration/net8.0" +$runId = [Guid]::NewGuid().ToString("N") +$artifacts = Join-Path $repo "artifacts/tlx/$RuntimeIdentifier/$runId" +$pluginPublish = Join-Path $artifacts "plugin-publish" +$source = Join-Path $artifacts "package" +$mlSource = Join-Path $artifacts "mlruntime-publish" +$mlStage = Join-Path $source "mlruntime" $out = Join-Path $PSScriptRoot "tlx" -dotnet build (Join-Path $repo "DiffSingerForTuneLab.csproj") -c $Configuration -dotnet build (Join-Path $repo "MLRuntime/MLRuntime.csproj") -c $Configuration +New-Item -ItemType Directory -Force -Path $pluginPublish, $source, $mlSource | Out-Null -# MLRuntime.exe 子进程:暂存进插件输出的 mlruntime/ 子目录(自带 onnxruntime + runtimes/),随后一并打进 .tlx。 -$mlSource = Join-Path $repo "MLRuntime/bin/$Configuration/net8.0" -$mlStage = Join-Path $source "mlruntime" -if (Test-Path $mlStage) { Remove-Item $mlStage -Recurse -Force } +function Invoke-DotNet { + & dotnet @args + if ($LASTEXITCODE -ne 0) { throw "dotnet failed with exit code $LASTEXITCODE" } +} + +# Run one pack script per clean checkout/runner. Do not launch multiple RIDs concurrently in the same worktree, +# because NuGet restore shares project obj files. +Invoke-DotNet restore (Join-Path $repo "DiffSingerForTuneLab.csproj") -r $RuntimeIdentifier -p:NuGetAudit=false +Invoke-DotNet restore (Join-Path $repo "MLRuntime/MLRuntime.csproj") -r $RuntimeIdentifier -p:NuGetAudit=false +Invoke-DotNet publish (Join-Path $repo "DiffSingerForTuneLab.csproj") ` + -c $Configuration -r $RuntimeIdentifier --self-contained false --no-restore -o $pluginPublish +Invoke-DotNet publish (Join-Path $repo "MLRuntime/MLRuntime.csproj") ` + -c $Configuration -r $RuntimeIdentifier --self-contained false --no-restore -o $mlSource + +# Build the package tree from publish outputs instead of deleting unwanted files in place. +# Managed/plugin content excludes flattened native assets; those are restored to runtimes//native below. +$nativeFileNames = @( + "DirectML.dll", "DirectML.pdb", "DirectML.Debug.dll", "DirectML.Debug.pdb", + "onnxruntime.dll", "onnxruntime.lib", "onnxruntime_providers_shared.dll", + "onnxruntime_providers_shared.lib", "libonnxruntime.dylib", "libonnxruntime.so" +) +Get-ChildItem $pluginPublish | Where-Object { $_.Name -notin $nativeFileNames } | + Copy-Item -Destination $source -Recurse -Force + +# Stage MLRuntime below the plugin, excluding its duplicate flattened ONNX native assets. New-Item -ItemType Directory -Force -Path $mlStage | Out-Null -Copy-Item -Path (Join-Path $mlSource "*") -Destination $mlStage -Recurse -Force - -# 去重 onnxruntime:MLRuntime 子进程经 OnnxNativeResolver 改从父插件目录 runtimes/ 加载原生库, -# 不再自带一份(省 ~15MB×平台)。删掉暂存里的 mlruntime/runtimes/(父目录整树 runtimes/ 仍在)。 -$mlRuntimes = Join-Path $mlStage "runtimes" -if (Test-Path $mlRuntimes) { Remove-Item $mlRuntimes -Recurse -Force } - -# 剪除输出【根目录】冗余的原生库副本 + DirectML 调试符号:规范副本在 runtimes//native/, -# 宿主经 deps.json(AssemblyDependencyResolver)从那里解析、根部这几份用不上。某些 SDK 版本 -# (如 CI 的 10.x)会把它们额外拷到输出根、白占 ~17MB,某些(本机 8.x)不拷——显式剪除以保证 -# 跨环境产物一致、精简。只删根部,runtimes/ 整树不动。 -foreach ($f in 'DirectML.dll','DirectML.pdb','DirectML.Debug.dll','DirectML.Debug.pdb','onnxruntime.dll','onnxruntime.lib','onnxruntime_providers_shared.dll','onnxruntime_providers_shared.lib') { - $p = Join-Path $source $f - if (Test-Path $p) { Remove-Item $p -Force } +Get-ChildItem $mlSource | Where-Object { $_.Name -notin $nativeFileNames -and $_.Name -ne "runtimes" } | + Copy-Item -Destination $mlStage -Recurse -Force +# Each publish uses one explicit RID, so only that RID's native assets are present. +$native = Join-Path $source "runtimes/$RuntimeIdentifier/native" +New-Item -ItemType Directory -Force -Path $native | Out-Null + +# RID publish flattens native assets into the publish root, while the plugin's deps.json retains their canonical +# runtimes//native paths. Restore that layout for TuneLab's AssemblyDependencyResolver. +$nativeFiles = if ($RuntimeIdentifier -eq "win-x64") { + @("DirectML.dll", "onnxruntime.dll", "onnxruntime.lib", "onnxruntime_providers_shared.dll", "onnxruntime_providers_shared.lib") +} elseif ($RuntimeIdentifier -eq "osx-arm64") { + @("libonnxruntime.dylib") +} else { + @("libonnxruntime.so") +} +foreach ($file in $nativeFiles) { + $rootCopy = Join-Path $pluginPublish $file + if (Test-Path $rootCopy) { Copy-Item $rootCopy (Join-Path $native $file) -Force } } -# 从 manifest.json 取 id + version 命名产物 -$desc = Get-Content (Join-Path $source "manifest.json") -Raw | ConvertFrom-Json -$tlx = Join-Path $out ("$($desc.id)-$($desc.version).tlx") +$onnxNativeName = if ($RuntimeIdentifier -eq "win-x64") { "onnxruntime.dll" } + elseif ($RuntimeIdentifier -eq "osx-arm64") { "libonnxruntime.dylib" } + else { "libonnxruntime.so" } +if (-not (Test-Path (Join-Path $native $onnxNativeName))) { + throw "Missing ONNX Runtime native library: $onnxNativeName" +} +if ($RuntimeIdentifier -eq "win-x64" -and -not (Test-Path (Join-Path $native "DirectML.dll"))) { + throw "Missing DirectML.dll in Windows package" +} + + +# A RID-specific package advertises only the RID it actually contains. +$manifestPath = Join-Path $source "manifest.json" +$manifest = Get-Content $manifestPath -Raw -Encoding utf8 | ConvertFrom-Json +$manifest.platforms = @($RuntimeIdentifier) +$manifestJson = $manifest | ConvertTo-Json -Depth 10 +[System.IO.File]::WriteAllText($manifestPath, $manifestJson, [System.Text.UTF8Encoding]::new($false)) + +$runtimeName = if ($RuntimeIdentifier -eq "win-x64") { "MLRuntime.exe" } else { "MLRuntime" } +$runtimePath = Join-Path $mlStage $runtimeName +if (-not (Test-Path $runtimePath)) { throw "Missing MLRuntime apphost: $runtimePath" } +if ($RuntimeIdentifier -ne "win-x64" -and -not $IsWindows) { + chmod +x $runtimePath +} New-Item -ItemType Directory -Force -Path $out | Out-Null -if (Test-Path $tlx) { Remove-Item $tlx -Force } +$tlx = Join-Path $out ("$($manifest.id)-$($manifest.version)-$RuntimeIdentifier.tlx") +if (Test-Path $tlx) { + $tlx = Join-Path $out ("$($manifest.id)-$($manifest.version)-$RuntimeIdentifier-$runId.tlx") +} [System.IO.Compression.ZipFile]::CreateFromDirectory($source, $tlx) -Write-Host "已打包 $tlx" +Write-Host "Packed $tlx" From bb10166019ddf0dd5268209e625ecb11e7d195d6 Mon Sep 17 00:00:00 2001 From: Kakaru <97896816+KakaruHayate@users.noreply.github.com> Date: Tue, 28 Jul 2026 23:47:54 +0800 Subject: [PATCH 2/6] Enhance PR test workflow for multi-OS support Refactor PR test workflow to support multiple OS builds and streamline restore and build steps. --- .github/workflows/pr-test.yml | 54 ++++++++++++++--------------------- 1 file changed, 21 insertions(+), 33 deletions(-) diff --git a/.github/workflows/pr-test.yml b/.github/workflows/pr-test.yml index 2aa3be1..d67ab65 100644 --- a/.github/workflows/pr-test.yml +++ b/.github/workflows/pr-test.yml @@ -1,7 +1,6 @@ # PR 验证工作流:构建 + 单元测试 + 冒烟测试编译 # 每次 PR 到 master 时自动触发,确保代码能编译、测试通过、冒烟测试可构建。 # 与 build-tlx.yml( nightly 打包 .tlx)互补:pr-test 轻量快速、tlx 构建重但产 artifact。 - name: PR Test on: @@ -12,7 +11,17 @@ on: jobs: build-and-test: - runs-on: windows-latest + strategy: + fail-fast: false + matrix: + include: + - os: windows-latest + rid: win-x64 + - os: macos-14 + rid: osx-arm64 + - os: ubuntu-latest + rid: linux-x64 + runs-on: ${{ matrix.os }} permissions: contents: read @@ -39,45 +48,24 @@ jobs: with: dotnet-version: 8.0.x - - name: Restore NuGet packages - shell: pwsh - run: | - Set-Location DiffSingerForTuneLab - # 主插件、测试项目、冒烟工具是三个独立项目(测试/工具不被主 csproj 引用, - # 裸 dotnet restore 只覆盖主插件),后续 build 步骤用 --no-restore,故此处逐个 restore。 - dotnet restore - dotnet restore tests/DiffSinger.Tests/DiffSinger.Tests.csproj - dotnet restore tools/DiffSingerSmokeTest/DiffSingerSmokeTest.csproj - - - name: Build plugin (Release) + - name: Restore projects shell: pwsh run: | - Set-Location DiffSingerForTuneLab - dotnet build --configuration Release --no-restore -v minimal + dotnet restore DiffSingerForTuneLab/DiffSingerForTuneLab.csproj -r ${{ matrix.rid }} + dotnet restore DiffSingerForTuneLab/MLRuntime/MLRuntime.csproj -r ${{ matrix.rid }} + dotnet restore DiffSingerForTuneLab/tests/DiffSinger.Tests/DiffSinger.Tests.csproj + dotnet restore DiffSingerForTuneLab/tools/DiffSingerSmokeTest/DiffSingerSmokeTest.csproj -r ${{ matrix.rid }} - - name: Build unit tests + - name: Build plugin and MLRuntime shell: pwsh run: | - Set-Location DiffSingerForTuneLab - dotnet build tests/DiffSinger.Tests/DiffSinger.Tests.csproj --configuration Release --no-restore -v minimal + dotnet build DiffSingerForTuneLab/DiffSingerForTuneLab.csproj -c Release -r ${{ matrix.rid }} --no-self-contained --no-restore -v minimal + dotnet build DiffSingerForTuneLab/MLRuntime/MLRuntime.csproj -c Release -r ${{ matrix.rid }} --no-self-contained --no-restore -v minimal - name: Run unit tests shell: pwsh - run: | - Set-Location DiffSingerForTuneLab - dotnet test tests/DiffSinger.Tests/DiffSinger.Tests.csproj --configuration Release --no-build --logger:"console;verbosity=normal" + run: dotnet test DiffSingerForTuneLab/tests/DiffSinger.Tests/DiffSinger.Tests.csproj -c Release --no-restore --logger:"console;verbosity=normal" - name: Build smoke test tool shell: pwsh - run: | - Set-Location DiffSingerForTuneLab - dotnet build tools/DiffSingerSmokeTest/DiffSingerSmokeTest.csproj --configuration Release --no-restore -v minimal - - - name: Upload test results - if: always() - uses: actions/upload-artifact@v4 - with: - name: test-results-${{ github.sha }} - path: | - DiffSingerForTuneLab/tests/**/TestResult*.xml - retention-days: 7 + run: dotnet build DiffSingerForTuneLab/tools/DiffSingerSmokeTest/DiffSingerSmokeTest.csproj -c Release -r ${{ matrix.rid }} --no-self-contained --no-restore -v minimal From b71cd3d06990def7349a497cc92164f4eaf4d3d6 Mon Sep 17 00:00:00 2001 From: Kakaru <97896816+KakaruHayate@users.noreply.github.com> Date: Tue, 28 Jul 2026 23:48:24 +0800 Subject: [PATCH 3/6] Enhance PR test workflow with descriptive comments Added comments to explain the PR test workflow. --- .github/workflows/pr-test.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/pr-test.yml b/.github/workflows/pr-test.yml index d67ab65..93bec44 100644 --- a/.github/workflows/pr-test.yml +++ b/.github/workflows/pr-test.yml @@ -1,6 +1,7 @@ # PR 验证工作流:构建 + 单元测试 + 冒烟测试编译 # 每次 PR 到 master 时自动触发,确保代码能编译、测试通过、冒烟测试可构建。 # 与 build-tlx.yml( nightly 打包 .tlx)互补:pr-test 轻量快速、tlx 构建重但产 artifact。 + name: PR Test on: From 212ca7c48c6fc05cb07e0c3ce70746fde8bfa40a Mon Sep 17 00:00:00 2001 From: Kakaru <97896816+KakaruHayate@users.noreply.github.com> Date: Tue, 28 Jul 2026 23:48:48 +0800 Subject: [PATCH 4/6] Refactor CI workflow for multi-platform builds --- .github/workflows/build-tlx.yml | 51 +++++++++++---------------------- 1 file changed, 16 insertions(+), 35 deletions(-) diff --git a/.github/workflows/build-tlx.yml b/.github/workflows/build-tlx.yml index d423afd..abb3900 100644 --- a/.github/workflows/build-tlx.yml +++ b/.github/workflows/build-tlx.yml @@ -20,7 +20,17 @@ on: jobs: build-tlx: - runs-on: windows-latest + strategy: + fail-fast: false + matrix: + include: + - os: windows-latest + rid: win-x64 + - os: macos-14 + rid: osx-arm64 + - os: ubuntu-latest + rid: linux-x64 + runs-on: ${{ matrix.os }} permissions: contents: read actions: read @@ -48,42 +58,13 @@ jobs: with: dotnet-version: 8.0.x - - name: Get short SHA - uses: benjlevesque/short-sha@v3.0 - id: short-sha - with: - length: 7 - - - name: Build & pack .tlx - shell: pwsh - run: | - # 单一事实来源:直接调用维护的打包脚本(与本地 dev 打包同一份逻辑),杜绝 CI 内联 - # 逻辑漂移。脚本内部会:构建主插件 + MLRuntime,装配 mlruntime/ 子进程(去重 - # onnxruntime),剪除输出根部冗余原生库/调试符号,产物落 tools/tlx/-.tlx。 - pwsh DiffSingerForTuneLab/tools/pack-tlx.ps1 -Configuration Release - - - name: Rename artifact with short SHA + - name: Pack RID-specific TLX shell: pwsh - run: | - $ErrorActionPreference = 'Stop' - $tlx = Get-ChildItem DiffSingerForTuneLab/tools/tlx/*.tlx | Select-Object -First 1 - if (-not $tlx) { Write-Error "未找到打包产物 .tlx" } - $dest = Join-Path $tlx.Directory.FullName "DiffSingerForTuneLab-${{ steps.short-sha.outputs.sha }}.tlx" - Move-Item $tlx.FullName $dest -Force - Write-Host ("产物: {0} ({1:N2} MB)" -f (Split-Path $dest -Leaf), ((Get-Item $dest).Length/1MB)) + run: pwsh DiffSingerForTuneLab/tools/pack-tlx.ps1 -Configuration Release -RuntimeIdentifier ${{ matrix.rid }} - - name: Upload .tlx artifact + - name: Upload TLX artifact uses: actions/upload-artifact@v4 with: - name: DiffSingerForTuneLab-${{ steps.short-sha.outputs.sha }} - path: DiffSingerForTuneLab/tools/tlx/DiffSingerForTuneLab-*.tlx + name: DiffSingerForTuneLab-${{ matrix.rid }}-${{ github.sha }} + path: DiffSingerForTuneLab/tools/tlx/*-${{ matrix.rid }}.tlx retention-days: 14 - - - name: Upload build log on failure - if: failure() - uses: actions/upload-artifact@v4 - with: - name: build-log-${{ steps.short-sha.outputs.sha }} - path: | - DiffSingerForTuneLab/**/bin/**/*.log - retention-days: 7 From 6ad79b2f7fbf73daa670e31a8eb307d4bededf27 Mon Sep 17 00:00:00 2001 From: KakaruHayate Date: Wed, 29 Jul 2026 00:38:46 +0800 Subject: [PATCH 5/6] fix: enforce published runtime platforms --- DiffSingerForTuneLab.csproj | 3 +- MLRuntime/MLRuntime.csproj | 3 +- OpenUtauFacade/OpenUtau.Core.csproj | 3 +- RuntimePlatform.cs | 59 +++++++++++-------- .../DiffSinger.Tests/RuntimePlatformTests.cs | 43 ++++++++++++++ .../DiffSingerSmokeTest.csproj | 3 +- tools/pack-tlx.ps1 | 28 ++++++++- 7 files changed, 113 insertions(+), 29 deletions(-) diff --git a/DiffSingerForTuneLab.csproj b/DiffSingerForTuneLab.csproj index 89d2704..de72b24 100644 --- a/DiffSingerForTuneLab.csproj +++ b/DiffSingerForTuneLab.csproj @@ -19,7 +19,8 @@ win-x64 osx-arm64 linux-x64 - $([System.String]::Copy('$(DiffSingerRuntimeIdentifier)').StartsWith('win-')) + True + False disable OpenUtau diff --git a/RuntimePlatform.cs b/RuntimePlatform.cs index af8b6b4..fc378a9 100644 --- a/RuntimePlatform.cs +++ b/RuntimePlatform.cs @@ -8,7 +8,10 @@ internal static class RuntimePlatform public const string CpuProvider = "cpu"; public const string DirectMlProvider = "directml"; - public static bool SupportsDirectML => OperatingSystem.IsWindows(); + public static string RuntimeIdentifier + => GetRuntimeIdentifier(CurrentOperatingSystem, RuntimeInformation.ProcessArchitecture); + + public static bool SupportsDirectML => RuntimeIdentifier == "win-x64"; public static string DefaultProvider => SupportsDirectML ? DirectMlProvider : CpuProvider; @@ -20,31 +23,39 @@ public static string NormalizeProvider(string? provider) public static bool IsDirectML(string? provider) => NormalizeProvider(provider) == DirectMlProvider; - public static string RuntimeExecutableName => OperatingSystem.IsWindows() ? "MLRuntime.exe" : "MLRuntime"; + public static string RuntimeExecutableName => RuntimeIdentifier switch + { + "win-x64" => "MLRuntime.exe", + "osx-arm64" or "linux-x64" => "MLRuntime", + _ => throw new InvalidOperationException("RuntimeIdentifier returned an unsupported RID."), + }; + + public static string OnnxRuntimeLibraryName => RuntimeIdentifier switch + { + "win-x64" => "onnxruntime.dll", + "osx-arm64" => "libonnxruntime.dylib", + "linux-x64" => "libonnxruntime.so", + _ => throw new InvalidOperationException("RuntimeIdentifier returned an unsupported RID."), + }; - public static string RuntimeIdentifier + internal static string GetRuntimeIdentifier(OSPlatform operatingSystem, Architecture architecture) { - get - { - var os = OperatingSystem.IsWindows() ? "win" - : OperatingSystem.IsMacOS() ? "osx" - : OperatingSystem.IsLinux() ? "linux" - : throw new PlatformNotSupportedException("DiffSinger 仅支持 Windows、macOS 和 Linux。"); - var architecture = RuntimeInformation.ProcessArchitecture switch - { - Architecture.X86 => "x86", - Architecture.X64 => "x64", - Architecture.Arm => "arm", - Architecture.Arm64 => "arm64", - _ => throw new PlatformNotSupportedException( - $"不支持的进程架构:{RuntimeInformation.ProcessArchitecture}"), - }; - return $"{os}-{architecture}"; - } + if (operatingSystem == OSPlatform.Windows && architecture == Architecture.X64) + return "win-x64"; + if (operatingSystem == OSPlatform.OSX && architecture == Architecture.Arm64) + return "osx-arm64"; + if (operatingSystem == OSPlatform.Linux && architecture == Architecture.X64) + return "linux-x64"; + + throw new PlatformNotSupportedException( + $"DiffSinger does not support {operatingSystem} on {architecture}. " + + "Supported platforms: win-x64, osx-arm64, linux-x64."); } - public static string OnnxRuntimeLibraryName => OperatingSystem.IsWindows() ? "onnxruntime.dll" - : OperatingSystem.IsMacOS() ? "libonnxruntime.dylib" - : OperatingSystem.IsLinux() ? "libonnxruntime.so" - : throw new PlatformNotSupportedException("DiffSinger 仅支持 Windows、macOS 和 Linux。"); + private static OSPlatform CurrentOperatingSystem + => OperatingSystem.IsWindows() ? OSPlatform.Windows + : OperatingSystem.IsMacOS() ? OSPlatform.OSX + : OperatingSystem.IsLinux() ? OSPlatform.Linux + : throw new PlatformNotSupportedException( + "DiffSinger supports only Windows, macOS, and Linux."); } diff --git a/tests/DiffSinger.Tests/RuntimePlatformTests.cs b/tests/DiffSinger.Tests/RuntimePlatformTests.cs index 9337452..51c2df2 100644 --- a/tests/DiffSinger.Tests/RuntimePlatformTests.cs +++ b/tests/DiffSinger.Tests/RuntimePlatformTests.cs @@ -1,3 +1,5 @@ +using System; +using System.Runtime.InteropServices; using DiffSingerForTuneLab; using Xunit; @@ -5,6 +7,38 @@ namespace DiffSinger.Tests; public sealed class RuntimePlatformTests { + [Theory] + [InlineData("windows", Architecture.X64, "win-x64")] + [InlineData("osx", Architecture.Arm64, "osx-arm64")] + [InlineData("linux", Architecture.X64, "linux-x64")] + public void GetRuntimeIdentifier_AcceptsPublishedPlatforms( + string operatingSystem, + Architecture architecture, + string expected) + => Assert.Equal( + expected, + RuntimePlatform.GetRuntimeIdentifier(ToPlatform(operatingSystem), architecture)); + + [Theory] + [InlineData("windows", Architecture.X86)] + [InlineData("windows", Architecture.Arm)] + [InlineData("windows", Architecture.Arm64)] + [InlineData("osx", Architecture.X86)] + [InlineData("osx", Architecture.X64)] + [InlineData("osx", Architecture.Arm)] + [InlineData("linux", Architecture.X86)] + [InlineData("linux", Architecture.Arm)] + [InlineData("linux", Architecture.Arm64)] + public void GetRuntimeIdentifier_RejectsUnpublishedPlatforms( + string operatingSystem, + Architecture architecture) + { + var exception = Assert.Throws( + () => RuntimePlatform.GetRuntimeIdentifier(ToPlatform(operatingSystem), architecture)); + + Assert.Contains("win-x64, osx-arm64, linux-x64", exception.Message); + } + [Fact] public void NormalizeProvider_AlwaysAcceptsCpu() => Assert.Equal(RuntimePlatform.CpuProvider, RuntimePlatform.NormalizeProvider("cpu")); @@ -41,4 +75,13 @@ public void RuntimeNamesMatchCurrentOperatingSystem() Assert.Equal("libonnxruntime.so", RuntimePlatform.OnnxRuntimeLibraryName); } } + + private static OSPlatform ToPlatform(string operatingSystem) + => operatingSystem switch + { + "windows" => OSPlatform.Windows, + "osx" => OSPlatform.OSX, + "linux" => OSPlatform.Linux, + _ => throw new ArgumentOutOfRangeException(nameof(operatingSystem)), + }; } diff --git a/tools/DiffSingerSmokeTest/DiffSingerSmokeTest.csproj b/tools/DiffSingerSmokeTest/DiffSingerSmokeTest.csproj index c6de3e4..bc57307 100644 --- a/tools/DiffSingerSmokeTest/DiffSingerSmokeTest.csproj +++ b/tools/DiffSingerSmokeTest/DiffSingerSmokeTest.csproj @@ -13,7 +13,8 @@ win-x64 osx-arm64 linux-x64 - $([System.String]::Copy('$(DiffSingerRuntimeIdentifier)').StartsWith('win-')) + True + False diff --git a/tools/pack-tlx.ps1 b/tools/pack-tlx.ps1 index fa7e35f..5ec8a7f 100644 --- a/tools/pack-tlx.ps1 +++ b/tools/pack-tlx.ps1 @@ -10,6 +10,29 @@ param( $ErrorActionPreference = "Stop" Add-Type -AssemblyName System.IO.Compression.FileSystem +$hostOperatingSystem = if ( + [System.Runtime.InteropServices.RuntimeInformation]::IsOSPlatform( + [System.Runtime.InteropServices.OSPlatform]::Windows) +) { + "win" +} elseif ( + [System.Runtime.InteropServices.RuntimeInformation]::IsOSPlatform( + [System.Runtime.InteropServices.OSPlatform]::OSX) +) { + "osx" +} elseif ( + [System.Runtime.InteropServices.RuntimeInformation]::IsOSPlatform( + [System.Runtime.InteropServices.OSPlatform]::Linux) +) { + "linux" +} else { + throw "Unsupported packaging host: $([System.Runtime.InteropServices.RuntimeInformation]::OSDescription)" +} +$targetOperatingSystem = $RuntimeIdentifier.Split("-")[0] +if ($targetOperatingSystem -ne $hostOperatingSystem) { + throw "Package $RuntimeIdentifier on a $targetOperatingSystem host; current host is $hostOperatingSystem. Unix packages must be created on Unix so MLRuntime retains executable permissions." +} + $repo = Split-Path $PSScriptRoot -Parent $runId = [Guid]::NewGuid().ToString("N") $artifacts = Join-Path $repo "artifacts/tlx/$RuntimeIdentifier/$runId" @@ -88,8 +111,11 @@ $manifestJson = $manifest | ConvertTo-Json -Depth 10 $runtimeName = if ($RuntimeIdentifier -eq "win-x64") { "MLRuntime.exe" } else { "MLRuntime" } $runtimePath = Join-Path $mlStage $runtimeName if (-not (Test-Path $runtimePath)) { throw "Missing MLRuntime apphost: $runtimePath" } -if ($RuntimeIdentifier -ne "win-x64" -and -not $IsWindows) { +if ($RuntimeIdentifier -ne "win-x64") { chmod +x $runtimePath + if ($LASTEXITCODE -ne 0) { + throw "Failed to mark MLRuntime executable: $runtimePath" + } } New-Item -ItemType Directory -Force -Path $out | Out-Null From 2bd1c11c4cb2fdf88c2b15d4452033f0496683c8 Mon Sep 17 00:00:00 2001 From: Kakaru <97896816+KakaruHayate@users.noreply.github.com> Date: Wed, 29 Jul 2026 11:00:00 +0800 Subject: [PATCH 6/6] fix --- tools/pack-tlx.ps1 | 256 ++++++++++++++++++++++----------------------- 1 file changed, 128 insertions(+), 128 deletions(-) diff --git a/tools/pack-tlx.ps1 b/tools/pack-tlx.ps1 index 5ec8a7f..132d074 100644 --- a/tools/pack-tlx.ps1 +++ b/tools/pack-tlx.ps1 @@ -1,128 +1,128 @@ -# Build one RID-specific .tlx package. The package root contains manifest.json, managed assemblies, -# runtimes//native/, and the framework-dependent MLRuntime apphost under mlruntime/. -# Usage: pwsh tools/pack-tlx.ps1 [-Configuration Release] [-RuntimeIdentifier win-x64|osx-arm64|linux-x64] -param( - [string]$Configuration = "Release", - [ValidateSet("win-x64", "osx-arm64", "linux-x64")] - [string]$RuntimeIdentifier = "win-x64" -) - -$ErrorActionPreference = "Stop" -Add-Type -AssemblyName System.IO.Compression.FileSystem - -$hostOperatingSystem = if ( - [System.Runtime.InteropServices.RuntimeInformation]::IsOSPlatform( - [System.Runtime.InteropServices.OSPlatform]::Windows) -) { - "win" -} elseif ( - [System.Runtime.InteropServices.RuntimeInformation]::IsOSPlatform( - [System.Runtime.InteropServices.OSPlatform]::OSX) -) { - "osx" -} elseif ( - [System.Runtime.InteropServices.RuntimeInformation]::IsOSPlatform( - [System.Runtime.InteropServices.OSPlatform]::Linux) -) { - "linux" -} else { - throw "Unsupported packaging host: $([System.Runtime.InteropServices.RuntimeInformation]::OSDescription)" -} -$targetOperatingSystem = $RuntimeIdentifier.Split("-")[0] -if ($targetOperatingSystem -ne $hostOperatingSystem) { - throw "Package $RuntimeIdentifier on a $targetOperatingSystem host; current host is $hostOperatingSystem. Unix packages must be created on Unix so MLRuntime retains executable permissions." -} - -$repo = Split-Path $PSScriptRoot -Parent -$runId = [Guid]::NewGuid().ToString("N") -$artifacts = Join-Path $repo "artifacts/tlx/$RuntimeIdentifier/$runId" -$pluginPublish = Join-Path $artifacts "plugin-publish" -$source = Join-Path $artifacts "package" -$mlSource = Join-Path $artifacts "mlruntime-publish" -$mlStage = Join-Path $source "mlruntime" -$out = Join-Path $PSScriptRoot "tlx" - -New-Item -ItemType Directory -Force -Path $pluginPublish, $source, $mlSource | Out-Null - -function Invoke-DotNet { - & dotnet @args - if ($LASTEXITCODE -ne 0) { throw "dotnet failed with exit code $LASTEXITCODE" } -} - -# Run one pack script per clean checkout/runner. Do not launch multiple RIDs concurrently in the same worktree, -# because NuGet restore shares project obj files. -Invoke-DotNet restore (Join-Path $repo "DiffSingerForTuneLab.csproj") -r $RuntimeIdentifier -p:NuGetAudit=false -Invoke-DotNet restore (Join-Path $repo "MLRuntime/MLRuntime.csproj") -r $RuntimeIdentifier -p:NuGetAudit=false -Invoke-DotNet publish (Join-Path $repo "DiffSingerForTuneLab.csproj") ` - -c $Configuration -r $RuntimeIdentifier --self-contained false --no-restore -o $pluginPublish -Invoke-DotNet publish (Join-Path $repo "MLRuntime/MLRuntime.csproj") ` - -c $Configuration -r $RuntimeIdentifier --self-contained false --no-restore -o $mlSource - -# Build the package tree from publish outputs instead of deleting unwanted files in place. -# Managed/plugin content excludes flattened native assets; those are restored to runtimes//native below. -$nativeFileNames = @( - "DirectML.dll", "DirectML.pdb", "DirectML.Debug.dll", "DirectML.Debug.pdb", - "onnxruntime.dll", "onnxruntime.lib", "onnxruntime_providers_shared.dll", - "onnxruntime_providers_shared.lib", "libonnxruntime.dylib", "libonnxruntime.so" -) -Get-ChildItem $pluginPublish | Where-Object { $_.Name -notin $nativeFileNames } | - Copy-Item -Destination $source -Recurse -Force - -# Stage MLRuntime below the plugin, excluding its duplicate flattened ONNX native assets. -New-Item -ItemType Directory -Force -Path $mlStage | Out-Null -Get-ChildItem $mlSource | Where-Object { $_.Name -notin $nativeFileNames -and $_.Name -ne "runtimes" } | - Copy-Item -Destination $mlStage -Recurse -Force -# Each publish uses one explicit RID, so only that RID's native assets are present. -$native = Join-Path $source "runtimes/$RuntimeIdentifier/native" -New-Item -ItemType Directory -Force -Path $native | Out-Null - -# RID publish flattens native assets into the publish root, while the plugin's deps.json retains their canonical -# runtimes//native paths. Restore that layout for TuneLab's AssemblyDependencyResolver. -$nativeFiles = if ($RuntimeIdentifier -eq "win-x64") { - @("DirectML.dll", "onnxruntime.dll", "onnxruntime.lib", "onnxruntime_providers_shared.dll", "onnxruntime_providers_shared.lib") -} elseif ($RuntimeIdentifier -eq "osx-arm64") { - @("libonnxruntime.dylib") -} else { - @("libonnxruntime.so") -} -foreach ($file in $nativeFiles) { - $rootCopy = Join-Path $pluginPublish $file - if (Test-Path $rootCopy) { Copy-Item $rootCopy (Join-Path $native $file) -Force } -} - -$onnxNativeName = if ($RuntimeIdentifier -eq "win-x64") { "onnxruntime.dll" } - elseif ($RuntimeIdentifier -eq "osx-arm64") { "libonnxruntime.dylib" } - else { "libonnxruntime.so" } -if (-not (Test-Path (Join-Path $native $onnxNativeName))) { - throw "Missing ONNX Runtime native library: $onnxNativeName" -} -if ($RuntimeIdentifier -eq "win-x64" -and -not (Test-Path (Join-Path $native "DirectML.dll"))) { - throw "Missing DirectML.dll in Windows package" -} - - -# A RID-specific package advertises only the RID it actually contains. -$manifestPath = Join-Path $source "manifest.json" -$manifest = Get-Content $manifestPath -Raw -Encoding utf8 | ConvertFrom-Json -$manifest.platforms = @($RuntimeIdentifier) -$manifestJson = $manifest | ConvertTo-Json -Depth 10 -[System.IO.File]::WriteAllText($manifestPath, $manifestJson, [System.Text.UTF8Encoding]::new($false)) - -$runtimeName = if ($RuntimeIdentifier -eq "win-x64") { "MLRuntime.exe" } else { "MLRuntime" } -$runtimePath = Join-Path $mlStage $runtimeName -if (-not (Test-Path $runtimePath)) { throw "Missing MLRuntime apphost: $runtimePath" } -if ($RuntimeIdentifier -ne "win-x64") { - chmod +x $runtimePath - if ($LASTEXITCODE -ne 0) { - throw "Failed to mark MLRuntime executable: $runtimePath" - } -} - -New-Item -ItemType Directory -Force -Path $out | Out-Null -$tlx = Join-Path $out ("$($manifest.id)-$($manifest.version)-$RuntimeIdentifier.tlx") -if (Test-Path $tlx) { - $tlx = Join-Path $out ("$($manifest.id)-$($manifest.version)-$RuntimeIdentifier-$runId.tlx") -} -[System.IO.Compression.ZipFile]::CreateFromDirectory($source, $tlx) - -Write-Host "Packed $tlx" +# Build one RID-specific .tlx package. The package root contains manifest.json, managed assemblies, +# runtimes//native/, and the framework-dependent MLRuntime apphost under mlruntime/. +# Usage: pwsh tools/pack-tlx.ps1 [-Configuration Release] [-RuntimeIdentifier win-x64|osx-arm64|linux-x64] +param( + [string]$Configuration = "Release", + [ValidateSet("win-x64", "osx-arm64", "linux-x64")] + [string]$RuntimeIdentifier = "win-x64" +) + +$ErrorActionPreference = "Stop" +Add-Type -AssemblyName System.IO.Compression.FileSystem + +$hostOperatingSystem = if ( + [System.Runtime.InteropServices.RuntimeInformation]::IsOSPlatform( + [System.Runtime.InteropServices.OSPlatform]::Windows) +) { + "win" +} elseif ( + [System.Runtime.InteropServices.RuntimeInformation]::IsOSPlatform( + [System.Runtime.InteropServices.OSPlatform]::OSX) +) { + "osx" +} elseif ( + [System.Runtime.InteropServices.RuntimeInformation]::IsOSPlatform( + [System.Runtime.InteropServices.OSPlatform]::Linux) +) { + "linux" +} else { + throw "Unsupported packaging host: $([System.Runtime.InteropServices.RuntimeInformation]::OSDescription)" +} +$targetOperatingSystem = $RuntimeIdentifier.Split("-")[0] +if ($targetOperatingSystem -ne $hostOperatingSystem) { + throw "Package $RuntimeIdentifier on a $targetOperatingSystem host; current host is $hostOperatingSystem. Unix packages must be created on Unix so MLRuntime retains executable permissions." +} + +$repo = Split-Path $PSScriptRoot -Parent +$runId = [Guid]::NewGuid().ToString("N") +$artifacts = Join-Path $repo "artifacts/tlx/$RuntimeIdentifier/$runId" +$pluginPublish = Join-Path $artifacts "plugin-publish" +$source = Join-Path $artifacts "package" +$mlSource = Join-Path $artifacts "mlruntime-publish" +$mlStage = Join-Path $source "mlruntime" +$out = Join-Path $PSScriptRoot "tlx" + +New-Item -ItemType Directory -Force -Path $pluginPublish, $source, $mlSource | Out-Null + +function Invoke-DotNet { + & dotnet @args + if ($LASTEXITCODE -ne 0) { throw "dotnet failed with exit code $LASTEXITCODE" } +} + +# Run one pack script per clean checkout/runner. Do not launch multiple RIDs concurrently in the same worktree, +# because NuGet restore shares project obj files. +Invoke-DotNet restore (Join-Path $repo "DiffSingerForTuneLab.csproj") -r $RuntimeIdentifier "-p:NuGetAudit=false" +Invoke-DotNet restore (Join-Path $repo "MLRuntime/MLRuntime.csproj") -r $RuntimeIdentifier "-p:NuGetAudit=false" +Invoke-DotNet publish (Join-Path $repo "DiffSingerForTuneLab.csproj") ` + -c $Configuration -r $RuntimeIdentifier --self-contained false --no-restore -o $pluginPublish +Invoke-DotNet publish (Join-Path $repo "MLRuntime/MLRuntime.csproj") ` + -c $Configuration -r $RuntimeIdentifier --self-contained false --no-restore -o $mlSource + +# Build the package tree from publish outputs instead of deleting unwanted files in place. +# Managed/plugin content excludes flattened native assets; those are restored to runtimes//native below. +$nativeFileNames = @( + "DirectML.dll", "DirectML.pdb", "DirectML.Debug.dll", "DirectML.Debug.pdb", + "onnxruntime.dll", "onnxruntime.lib", "onnxruntime_providers_shared.dll", + "onnxruntime_providers_shared.lib", "libonnxruntime.dylib", "libonnxruntime.so" +) +Get-ChildItem $pluginPublish | Where-Object { $_.Name -notin $nativeFileNames } | + Copy-Item -Destination $source -Recurse -Force + +# Stage MLRuntime below the plugin, excluding its duplicate flattened ONNX native assets. +New-Item -ItemType Directory -Force -Path $mlStage | Out-Null +Get-ChildItem $mlSource | Where-Object { $_.Name -notin $nativeFileNames -and $_.Name -ne "runtimes" } | + Copy-Item -Destination $mlStage -Recurse -Force +# Each publish uses one explicit RID, so only that RID's native assets are present. +$native = Join-Path $source "runtimes/$RuntimeIdentifier/native" +New-Item -ItemType Directory -Force -Path $native | Out-Null + +# RID publish flattens native assets into the publish root, while the plugin's deps.json retains their canonical +# runtimes//native paths. Restore that layout for TuneLab's AssemblyDependencyResolver. +$nativeFiles = if ($RuntimeIdentifier -eq "win-x64") { + @("DirectML.dll", "onnxruntime.dll", "onnxruntime.lib", "onnxruntime_providers_shared.dll", "onnxruntime_providers_shared.lib") +} elseif ($RuntimeIdentifier -eq "osx-arm64") { + @("libonnxruntime.dylib") +} else { + @("libonnxruntime.so") +} +foreach ($file in $nativeFiles) { + $rootCopy = Join-Path $pluginPublish $file + if (Test-Path $rootCopy) { Copy-Item $rootCopy (Join-Path $native $file) -Force } +} + +$onnxNativeName = if ($RuntimeIdentifier -eq "win-x64") { "onnxruntime.dll" } + elseif ($RuntimeIdentifier -eq "osx-arm64") { "libonnxruntime.dylib" } + else { "libonnxruntime.so" } +if (-not (Test-Path (Join-Path $native $onnxNativeName))) { + throw "Missing ONNX Runtime native library: $onnxNativeName" +} +if ($RuntimeIdentifier -eq "win-x64" -and -not (Test-Path (Join-Path $native "DirectML.dll"))) { + throw "Missing DirectML.dll in Windows package" +} + + +# A RID-specific package advertises only the RID it actually contains. +$manifestPath = Join-Path $source "manifest.json" +$manifest = Get-Content $manifestPath -Raw -Encoding utf8 | ConvertFrom-Json +$manifest.platforms = @($RuntimeIdentifier) +$manifestJson = $manifest | ConvertTo-Json -Depth 10 +[System.IO.File]::WriteAllText($manifestPath, $manifestJson, [System.Text.UTF8Encoding]::new($false)) + +$runtimeName = if ($RuntimeIdentifier -eq "win-x64") { "MLRuntime.exe" } else { "MLRuntime" } +$runtimePath = Join-Path $mlStage $runtimeName +if (-not (Test-Path $runtimePath)) { throw "Missing MLRuntime apphost: $runtimePath" } +if ($RuntimeIdentifier -ne "win-x64") { + chmod +x $runtimePath + if ($LASTEXITCODE -ne 0) { + throw "Failed to mark MLRuntime executable: $runtimePath" + } +} + +New-Item -ItemType Directory -Force -Path $out | Out-Null +$tlx = Join-Path $out ("$($manifest.id)-$($manifest.version)-$RuntimeIdentifier.tlx") +if (Test-Path $tlx) { + $tlx = Join-Path $out ("$($manifest.id)-$($manifest.version)-$RuntimeIdentifier-$runId.tlx") +} +[System.IO.Compression.ZipFile]::CreateFromDirectory($source, $tlx) + +Write-Host "Packed $tlx"