xamarin / xamarin-macios

.NET for iOS, Mac Catalyst, macOS, and tvOS provide open-source bindings of the Apple SDKs for use with .NET managed languages such as C#
Other
2.49k stars 515 forks source link

InstallNameTool task fails with 2 nugets providing the same native library #15173

Closed ylatuya closed 1 year ago

ylatuya commented 2 years ago

Steps to Reproduce

  1. Create a net6.0-macos application
  2. Include 2 NuGet packages that provide native libraries with the same name eg: fluendo.gtk.osx/2.24.30.44/runtime/osx/native/libgio-2.0.0.dylib and fluendo.sdk.osx/1.3.0.385/runtime/osx/native/libgio-2.0.0.dylib
  3. Build the application
  4. The InstallNameTool will fail failing with the error: `System.IO.FileNotFoundException: Could not find file '***/src/LongoMatch/obj/netframework/Debug/net6.0-macos/osx-x64/nativelibraries/libgio-2.0.0.dylib.tmp'.

Expected Behavior

The app bundle is created correctly

Actual Behavior

The app bundle creation fails in the InstallNameTool tasks due to a race in the re-identification of files. 2 different processes are using the same output file name: libgio-2.0.0.dylib.

Environment

See build logs

Build Logs

msbuild.binlog.zip

Example Project (If Possible)

ylatuya commented 2 years ago

In case someone has the same issue, this is workaround that pre-processes the list of libraries to relocate:

  <UsingTask
    TaskName="RemoveReindentifyDuplicates"
    TaskFactory="RoslynCodeTaskFactory"
    AssemblyFile="$(MSBuildToolsPath)\Microsoft.Build.Tasks.Core.dll" >
    <ParameterGroup>
      <Files ParameterType="Microsoft.Build.Framework.ITaskItem[]" Required="true" />
      <Result ParameterType="Microsoft.Build.Framework.ITaskItem[]" Output="true" />
    </ParameterGroup>
    <Task>
      <Using Namespace="System.Collections.Generic"/>
      <Code Type="Fragment" Language="cs">
<![CDATA[
      if (Files.Length > 0)
      {
        var itemsDict = new Dictionary<string, ITaskItem> ();
        for (int i = 0; i < Files.Length; i++)
        {
          ITaskItem item = Files[i];
          string targetPath = item.GetMetadata("TargetPath");
          itemsDict[targetPath] = item;
        }
        Result = itemsDict.Values.ToArray();
      }
]]>
      </Code>
    </Task>
  </UsingTask>

    <Target Name="CleanDynamicLibrariesToReidentify"  AfterTargets="_ComputeDynamicLibrariesToReidentify" BeforeTargets="_InstallNameTool">
    <RemoveReindentifyDuplicates Files="@(_DynamicLibraryToReidentify)">
      <Output TaskParameter="Result" ItemName="_DistinctDynamicLibraryToReidentify" />
    </RemoveReindentifyDuplicates>
      <ItemGroup>
            <_DynamicLibraryToReidentify Remove="@(_DynamicLibraryToReidentify)" />
        <_DynamicLibraryToReidentify Include="@(_DistinctDynamicLibraryToReidentify)" />
      </ItemGroup>
    </Target>
ylatuya commented 2 years ago

The clean-up needs to be done a little bit earlier, in the list of _FileNativeReference.

    <UsingTask TaskName="RemoveFileNativeReferenceDuplicates" TaskFactory="RoslynCodeTaskFactory" AssemblyFile="$(MSBuildToolsPath)\Microsoft.Build.Tasks.Core.dll">
        <ParameterGroup>
            <Files ParameterType="Microsoft.Build.Framework.ITaskItem[]" Required="true" />
            <Result ParameterType="Microsoft.Build.Framework.ITaskItem[]" Output="true" />
        </ParameterGroup>
        <Task>
            <Using Namespace="System.Collections.Generic" />
            <Code Type="Fragment" Language="cs">
<![CDATA[
            if (Files.Length > 0)
            {
                var itemsDict = new Dictionary<string, ITaskItem> ();
                for (int i = 0; i < Files.Length; i++)
                {
                    ITaskItem item = Files[i];
                    string targetPath = item.GetMetadata("RelativePath");
                    itemsDict[targetPath] = item;
                }
                Result = itemsDict.Values.ToArray();
            }
]]>
            </Code>
        </Task>
    </UsingTask>

    <Target Name="CleanFileNativeReference" AfterTargets="_ComputeFrameworkFilesToPublish" BeforeTargets="_ComputeDynamicLibrariesToReidentify">
        <RemoveFileNativeReferenceDuplicates Files="@(_FileNativeReference)">
            <Output TaskParameter="Result" ItemName="_DistincFileNativeReference" />
        </RemoveFileNativeReferenceDuplicates>
        <ItemGroup>
                <_FileNativeReference Remove="@(_FileNativeReference)" />
                <_FileNativeReference Include="@(_DistincFileNativeReference)" />
        </ItemGroup>
    </Target>
ylatuya commented 2 years ago

@rolfbjarne I have found a new scenario in which it's impossible to fix this issue even with the pre-processing step posted in my previous comments. The issue can be reproduced in a project that includes the following packages:

These packages includes files with the same name that will be installed in different output directories (they have different RelativePath):

Screenshot 2022-11-10 at 11 03 05

Since in this example we have 2 files with the same name that are going to be installed in different places, we can't use the filtering method. That method worked because the RelativePath was the same so we were only considering the last frame.

This bug makes it impossible to use popular nuget's that uses CEF, such as the WebViewControl-Avalonia one. Would it make sense to prioritize it for .Net 7?

ylatuya commented 2 years ago

@rolfbjarne I have found a new scenario in which it's impossible to fix this issue even with the pre-processing step posted in my previous comments. The issue can be reproduced in a project that includes the following packages:

* cefglue.common

* microsoft.netcore.app.runtime.osx-x64

These packages includes files with the same name that will be installed in different output directories (they have different RelativePath):

* cefglue.common/91.4472.31/bin/osx/libclrjit.dylib -> RelativePath = Contents\MonoBundle/\CefGlueBrowserProcess\libclrjit.dylib

* microsoft.netcore.app.runtime.osx-x64/6.0.11/runtimes/osx-x64/native/libclrjit.dylib -> RelativePath = Contents\MonoBundle/libclrjit.dylib
Screenshot 2022-11-10 at 11 03 05

Since in this example we have 2 files with the same name that are going to be installed in different places, we can't use the filtering method. That method worked because the RelativePath was the same so we were only considering the last frame.

This bug makes it impossible to use popular nuget's that uses CEF, such as the WebViewControl-Avalonia one. Would it make sense to prioritize it for .Net 7?

Since this is a slightly different bug, I will open a new issue for it. I have already a patch that fixes the problem.