如何通过.targets文件让NuGet包按Runtime Identifier引用对应OS专属包
问题描述
- 正在创建多个OS专属NuGet包,每个包包含体积较大的原生进程,无法合并为单个包
- 包列表示例:
- MyProject.win-x86-x64
- MyProject.macos-x86-x64
- MyProject.ubuntu1804-x86-x64
- MyProject.ubuntu1804-arm64
- MyProject.debian10-x86-x64
- ...
- 需求:创建一个netstandard2.0包,通过
build/buildTransitive目录的.targets文件,根据当前OS的Runtime Identifier(RID)自动引用对应OS专属包 - 现有问题:
$([MSBuild]::IsOsPlatform())只能识别Windows/OSX/Linux,无法区分具体Linux发行版;不知道如何整合Microsoft.DotNet.PlatformAbstractions的RID检测逻辑到.targets中
解决方案
方法一:利用MSBuild内置命令检测Linux发行版(无需额外代码)
直接在.targets文件中执行系统命令读取OS信息,判断发行版和架构后添加对应包引用:
创建build/MyProject.targets文件,内容如下:
<Project> <!-- 构建时指定了RID的情况:直接用指定的RID引用包 --> <ItemGroup Condition="'$(RuntimeIdentifier)' != ''"> <PackageReference Include="MyProject.$(RuntimeIdentifier)" Version="1.0.0" PrivateAssets="all" /> </ItemGroup> <!-- Windows自动检测 --> <ItemGroup Condition="'$(RuntimeIdentifier)' == '' and $([MSBuild]::IsOsPlatform('Windows'))"> <PackageReference Include="MyProject.win-x86-x64" Version="1.0.0" PrivateAssets="all" /> </ItemGroup> <!-- macOS自动检测 --> <ItemGroup Condition="'$(RuntimeIdentifier)' == '' and $([MSBuild]::IsOsPlatform('OSX'))"> <PackageReference Include="MyProject.macos-x86-x64" Version="1.0.0" PrivateAssets="all" /> </ItemGroup> <!-- Linux发行版与架构检测 --> <Target Name="DetectLinuxRID" BeforeTargets="ResolvePackageReferences" Condition="$([MSBuild]::IsOsPlatform('Linux')) and '$(RuntimeIdentifier)' == ''"> <!-- 判断是否为Ubuntu 18.04 --> <Exec Command="grep -qE '^ID=ubuntu|^ID_LIKE=ubuntu' /etc/os-release && grep -qE '^VERSION_ID="18.04"' /etc/os-release" ConsoleToMSBuild="true" IgnoreExitCode="true"> <Output TaskParameter="ExitCode" PropertyName="IsUbuntu1804" /> </Exec> <!-- 判断是否为Debian 10 --> <Exec Command="grep -qE '^ID=debian' /etc/os-release && grep -qE '^VERSION_ID="10"' /etc/os-release" ConsoleToMSBuild="true" IgnoreExitCode="true"> <Output TaskParameter="ExitCode" PropertyName="IsDebian10" /> </Exec> <!-- 获取系统架构 --> <Exec Command="uname -m" ConsoleToMSBuild="true"> <Output TaskParameter="ConsoleOutput" PropertyName="LinuxArch" /> </Exec> <!-- 设置对应RID并添加包引用 --> <PropertyGroup> <CurrentRID Condition="$(IsUbuntu1804) == 0 and $(LinuxArch.Trim()) == 'x86_64'">ubuntu1804-x86-x64</CurrentRID> <CurrentRID Condition="$(IsUbuntu1804) == 0 and $(LinuxArch.Trim()) == 'aarch64'">ubuntu1804-arm64</CurrentRID> <CurrentRID Condition="$(IsDebian10) == 0 and $(LinuxArch.Trim()) == 'x86_64'">debian10-x86-x64</CurrentRID> </PropertyGroup> <ItemGroup Condition="'$(CurrentRID)' != ''"> <PackageReference Include="MyProject.$(CurrentRID)" Version="1.0.0" PrivateAssets="all" /> </ItemGroup> </Target> </Project>
方法二:自定义MSBuild任务实现RID检测(更可靠)
提取Microsoft.DotNet.PlatformAbstractions的RID检测逻辑,写成自定义MSBuild任务,避免依赖已弃用的包:
- 创建自定义MSBuild任务项目
创建一个.NET类库项目,添加以下代码:
using Microsoft.Build.Framework; using Microsoft.Build.Utilities; using System; using System.IO; using System.Runtime.InteropServices; public class GetCurrentRID : Task { [Output] public string CurrentRID { get; set; } public override bool Execute() { CurrentRID = ResolveCurrentRID(); return !string.IsNullOrEmpty(CurrentRID); } private string ResolveCurrentRID() { if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) { return "win-x86-x64"; } if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX)) { return "macos-x86-x64"; } if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux)) { var distro = ResolveLinuxDistribution(); var arch = RuntimeInformation.ProcessArchitecture switch { Architecture.X64 => "x86-x64", Architecture.Arm64 => "arm64", _ => null }; if (distro != null && arch != null) { return $"{distro}-{arch}"; } } return null; } private string ResolveLinuxDistribution() { if (File.Exists("/etc/os-release")) { var content = File.ReadAllText("/etc/os-release"); var lines = content.Split(new[] { '\n', '\r' }, StringSplitOptions.RemoveEmptyEntries); string id = null; string versionId = null; foreach (var line in lines) { if (line.StartsWith("ID=", StringComparison.OrdinalIgnoreCase)) { id = line.Split('=')[1].Trim('"', '\''); } else if (line.StartsWith("VERSION_ID=", StringComparison.OrdinalIgnoreCase)) { versionId = line.Split('=')[1].Trim('"', '\''); } } if (id == "ubuntu" && versionId == "18.04") { return "ubuntu1804"; } if (id == "debian" && versionId == "10") { return "debian10"; } } return null; } }
- 编译并引用任务到.targets
编译该类库得到YourRIDTask.dll,将其放在NuGet包的tools目录下,然后在build/MyProject.targets中引用:
<Project> <UsingTask TaskName="GetCurrentRID" AssemblyFile="$(MSBuildThisFileDirectory)../tools/YourRIDTask.dll" /> <Target Name="DetectRID" BeforeTargets="ResolvePackageReferences" Condition="'$(RuntimeIdentifier)' == ''"> <GetCurrentRID> <Output TaskParameter="CurrentRID" PropertyName="CurrentRID" /> </GetCurrentRID> <ItemGroup Condition="'$(CurrentRID)' != ''"> <PackageReference Include="MyProject.$(CurrentRID)" Version="1.0.0" PrivateAssets="all" /> </ItemGroup> </Target> <!-- 优先使用指定的RID --> <ItemGroup Condition="'$(RuntimeIdentifier)' != ''"> <PackageReference Include="MyProject.$(RuntimeIdentifier)" Version="1.0.0" PrivateAssets="all" /> </ItemGroup> </Project>
关键注意事项
- 设置
PrivateAssets="all"可避免OS专属包被传递到依赖项目中 - 若用户构建时通过
-r参数指定了RID,会优先使用该RID对应的包,支持交叉编译 - Linux发行版检测逻辑可根据实际支持的版本扩展,比如添加更多发行版的判断
内容的提问来源于stack exchange,提问作者Anthony Simmon
相关产品推荐
相关产品推荐

