【问题标题】:csproj copy files depending on operating systemcsproj 复制文件取决于操作系统
【发布时间】:2023-06-20 20:03:01
【问题描述】:

我正在使用 .NET Core 构建一个跨平台的类库。根据使用 .csproj 文件构建 C# .NET Core 项目的操作系统,我需要将本机库复制到项目的输出目录。例如,对于 OS X 我想复制一个 .dylib 文件,对于 Windows 我想复制一个 .DLL 文件,对于 Linux 我想复制一个 .so 文件。

如何使用 .csproj ItemGroup 中的 Condition 子句做到这一点?

  <ItemGroup>
    <Content Include="libNative.dylib" Condition=" '$(Configuration)|$(Platform)' == 'Debug|OSX' ">
      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
    </Content>
  </ItemGroup>   

$(Platform) 似乎不起作用。我可以使用其他变量吗?

【问题讨论】:

  • $(Platform) 具有不同的值,例如 Any CPU、x86、x64。我会检查它是如何为具有特定于平台的依赖项的开源库完成的。据我所知,有些人通过单独的 nuget 包提供此类特定于平台的依赖项。就像他们在nuget.org/packages/CoreCompat.System.Drawing/1.0.0-beta006中所做的一样@
  • 当您针对 OSX 构建时,$(Platform) 变量的值是多少?
  • Platform=AnyCPU 但对于 Windows 和 Linux 版本也是如此。
  • 最“干净”的方法是制作一个 NuGet 包,其中包含不同运行时文件夹中的所有本机库资产。当您引用这样的 NuGet 时,您将免费获得自动查找。集成到构建中时,需要构建 3 次,每个平台一次。这对于独立的应用程序来说是可以的,但对于便携式应用程序来说却不是。
  • @MartinUllrich 是否有一些示例项目可以说明如何做到这一点?

标签: c# .net .net-core csproj


【解决方案1】:

为了区分 Windows 和 Mac/Linux,您可以使用 $(os) 属性:这为您提供了用于 Windows 的 Windows_NT 和用于 Mac/Linux 的 UNIX

为了区分 Mac 和 Linux,至少在最新版本的 MSBuild 上,您可以使用 RuntimeInformation.IsOSPlatform

所以,结合起来,你的 csproj 可以有这样的东西:

<ItemGroup>
    <Content Include="libNative.dll" Condition=" '$(OS)' == 'Windows_NT' ">
      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
    </Content>
    <Content Include="libNative.so" Condition=" '$([System.Runtime.InteropServices.RuntimeInformation]::IsOSPlatform($([System.Runtime.InteropServices.OSPlatform]::Linux)))' ">
      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
    </Content>
    <Content Include="libNative.dylib" Condition=" '$([System.Runtime.InteropServices.RuntimeInformation]::IsOSPlatform($([System.Runtime.InteropServices.OSPlatform]::OSX)))' ">
      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
    </Content>
</ItemGroup>

据我所知,这应该适用于所有最新版本的 .Net Core、Mono 和 .Net Framework。

在旧版本中,您需要借助邪恶的诡计来区分 Mac 和 Linux:

对于 Linux -> Condition=" '$(OS)' == 'Unix' and ! $([System.IO.File]::Exists('/usr/lib/libc.dylib')) "

对于 Mac -> Condition=" '$(OS)' == 'Unix' and $([System.IO.File]::Exists('/usr/lib/libc.dylib')) "

来源:

【讨论】:

    【解决方案2】:

    当库第一次初始化时,我确实遇到了这样的问题,最终将 dll 保存在资源中并为 X86 或 X64 加载 dll。这对我来说是最干净的方式。

    当您在 nuget 中包含 dll 时,您还必须确保在 som 删除/清理 bin mapp 时重新复制相同的 dll。

    因此您必须将文件添加到 nuger 并在构建文件上创建一个目标以在构建时重新复制 dll

    这就是我所做的。

    internal class EmbeddedDllClass
    {
        public static void LoadAllDll()
        {
            Assembly assem = Assembly.GetExecutingAssembly();
            if (IntPtr.Size == 8)
            {
                var path = Path.Combine(string.Join("\\", assem.Location.Split('\\').Reverse().Skip(1).Reverse()), "x64");
    
                if (!Directory.Exists(path))
                    Directory.CreateDirectory(path);
    
                path = Path.Combine(path, "SQLite.Interop.dll");
    
                if (!File.Exists(path))
                    File.WriteAllBytes(path, Properties.Resources.SQLite_Interop_64);
            }
            else if (IntPtr.Size == 4)
            {
                var path = Path.Combine(string.Join("\\", assem.Location.Split('\\').Reverse().Skip(1).Reverse()), "x86");
    
                if (!Directory.Exists(path))
                    Directory.CreateDirectory(path);
    
                path = Path.Combine(path, "SQLite.Interop.dll");
    
                if (!File.Exists(path))
                    File.WriteAllBytes(path, Properties.Resources.SQLite_Interop_86);
    
            }
        }
    }
    

    然后就调用它

    EmbeddedDllClass.LoadAllDll();
    

    【讨论】:

      【解决方案3】:

      也想为我的 Nuget 包创建跨平台。 在 .csproj 中尝试了以下代码。

      <Content Include="libNative.win" Condition=" '$(OS)' == 'Windows_NT' ">
      <Content Include="libNative.dylib" Condition=" '$(OS)' != 'Windows_NT' ">
      

      因此创建了 .nupkg。

      当在 Linux 上引用相同的 .nupkg 时,因为 $(OS) 已经设置为 Windows_NT,添加了 libNative.win 而不是 libNative.dylib(这是预期的)。

      所以这个 $(OS) 是在创建 nuget 时设置的,而不是在添加时设置的。 更多详情:您可以在&lt;Description&gt; 中使用 $(OS) 或 $(Platform) 并查看值是如何设置的。

      【讨论】:

      • 您好,欢迎来到 *!您的答案似乎没有提供区分 MacOS 和 Linux 的解决方案。在这个问题上你有什么线索吗?
      【解决方案4】:

      作为@tzachs 接受答案的后续行动,从 msbuild 15.3(基本上是 Visual Studio 2017+ 或 .NET Core 2+)开始,您可以缩短它以使用 [MSBuild]::IsOSPlatform() 方法。 (this page 上的文档。)

      它接受OSPlatform struct 的值,例如WindowsLinuxOSXFreeBSD

      <ItemGroup>
          <Content Include="libNative.dll" Condition="$([MSBuild]::IsOSPlatform('Windows'))">
            <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
          </Content>
          <Content Include="libNative.so" Condition="$([MSBuild]::IsOSPlatform('Linux'))">
            <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
          </Content>
          <Content Include="libNative.dylib" Condition="$([MSBuild]::IsOSPlatform('OSX'))">
            <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
          </Content>
      </ItemGroup>
      

      【讨论】: