Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Binding project using xcframework can't be used in Xamarin.iOS app #10774

Closed
leonluc-dev opened this issue Mar 3, 2021 · 34 comments
Closed

Binding project using xcframework can't be used in Xamarin.iOS app #10774

leonluc-dev opened this issue Mar 3, 2021 · 34 comments
Labels
documentation The issue or pull request is about documentation iOS Issues affecting Xamarin.iOS
Milestone

Comments

@leonluc-dev
Copy link

leonluc-dev commented Mar 3, 2021

Steps to Reproduce

  1. Create a Binding project with a xcframework as the native framework reference
  2. Build binding project and reference it in a Xamarin.iOS app.
  3. Build the Xamarin.iOS app

Expected Behavior

The build process includes the native reference (or derived framework in case of xcframework) used in the referenced binding project.

Actual Behavior

If a xcframework was used as a native reference in the binding project the expected derived framework isn't included in the Xamarin.iOS build process, which causes builds to fail with a MT5211 error.

I've added an example project showcasing the issue below (using the Universal.xcframework that was supplied for xcframework testing).

Environment

=== Visual Studio Professional 2019 for Mac ===

Version 8.9 (build 1651)
Installation UUID: 57691245-242a-4da8-98b8-14c6159c8d17
	GTK+ 2.24.23 (Raleigh theme)
	Xamarin.Mac 6.18.0.23 (d16-6 / 088c73638)

	Package version: 612000122

=== Mono Framework MDK ===

Runtime:
	Mono 6.12.0.122 (2020-02/c621c35ffa0) (64-bit)
	Package version: 612000122

=== Roslyn (Language Service) ===

3.9.0-5.21112.8+f3ff04378c972d435826e6181de364b3c0db8d14

=== NuGet ===

Version: 5.8.0.6860

=== .NET Core SDK ===

SDK: /usr/local/share/dotnet/sdk/5.0.103/Sdks
SDK Versions:
	5.0.103
	5.0.102
	5.0.101
	5.0.100
	3.1.406
	3.1.405
	3.1.404
	3.1.403
	3.1.402
	3.1.401
	3.1.302
	3.1.301
MSBuild SDKs: /Applications/Visual Studio.app/Contents/Resources/lib/monodevelop/bin/MSBuild/Current/bin/Sdks

=== .NET Core Runtime ===

Runtime: /usr/local/share/dotnet/dotnet
Runtime Versions:
	5.0.3
	5.0.2
	5.0.1
	5.0.0
	3.1.12
	3.1.11
	3.1.10
	3.1.9
	3.1.8
	3.1.7
	3.1.6
	3.1.5
	2.1.23
	2.1.22
	2.1.21
	2.1.20
	2.1.19

=== .NET Core 3.1 SDK ===

SDK: 3.1.406

=== Xamarin.Profiler ===

Version: 1.6.15.68
Location: /Applications/Xamarin Profiler.app/Contents/MacOS/Xamarin Profiler

=== Updater ===

Version: 11

=== Apple Developer Tools ===

Xcode 12.4 (17801)
Build 12D4e

=== Xamarin.Mac ===

Xamarin.Mac not installed. Can't find /Library/Frameworks/Xamarin.Mac.framework/Versions/Current/Version.

=== Xamarin.iOS ===

Version: 14.14.2.5 (Visual Studio Professional)
Hash: 3836759d4
Branch: d16-9
Build date: 2021-02-10 17:56:44-0500

=== Xamarin Designer ===

Version: 16.9.0.316
Hash: bd2705417
Branch: remotes/origin/d16-9
Build date: 2021-02-24 00:16:08 UTC

=== Xamarin.Android ===

Version: 11.2.0.0 (Visual Studio Professional)
Commit: xamarin-android/d16-9/f908d16
Android SDK: /Users/appdeveloper/Library/Developer/Xamarin/android-sdk-macosx
	Supported Android versions:
		None installed

SDK Tools Version: 26.1.1
SDK Platform Tools Version: 30.0.5
SDK Build Tools Version: 30.0.3

Build Information: 
Mono: 5e9cb6d
Java.Interop: xamarin/java.interop/d16-9@1d382be
ProGuard: Guardsquare/proguard/v7.0.1@912d149
SQLite: xamarin/sqlite/3.32.2@cfe06e0
Xamarin.Android Tools: xamarin/xamarin-android-tools/main@ad80a42

=== Microsoft OpenJDK for Mobile ===

Java SDK: /Users/appdeveloper/Library/Developer/Xamarin/jdk/microsoft_dist_openjdk_1.8.0.25
1.8.0-25
Android Designer EPL code available here:
https://github.com/xamarin/AndroidDesigner.EPL

=== Android SDK Manager ===

Version: 16.9.0.22
Hash: a391de2
Branch: remotes/origin/d16-9
Build date: 2021-02-18 03:14:56 UTC

=== Android Device Manager ===

Version: 16.9.0.17
Hash: fc2b3db
Branch: remotes/origin/d16-9
Build date: 2021-02-18 03:15:18 UTC

=== Build Information ===

Release ID: 809001651
Git revision: c05b1dec4362b5956d47624a437e900ffdc9aa0a
Build date: 2021-02-25 11:27:51-05
Build branch: release-8.9
Xamarin extensions: c05b1dec4362b5956d47624a437e900ffdc9aa0a

=== Operating System ===

Mac OS X 10.16.0
Darwin 20.2.0 Darwin Kernel Version 20.2.0
    Wed Dec  2 20:39:59 PST 2020
    root:xnu-7195.60.75~1/RELEASE_X86_64 x86_64

Example Project (If Possible)

BindingTest.zip

@leonluc-dev
Copy link
Author

leonluc-dev commented Mar 4, 2021

I've did some more testing and it seems that including the xcframework in the Xamarin.iOS app directly and not in the binding project it seems to works fine (the proper architecture in the xcframework gets picked for both simulator and device builds in the build process and the code called through the API binding works fine).

So this seems to be a issue related to the build process "importing" the xcframework from a reference project/dll/assembly, rather than a xcframework support issue as a whole.

So if you have access to the xcframework of the library you want to use you can include it in the Xamarin.iOS project directly as a temporary workaround until this issue is fixed.
I've added an example showing the workaround:

BindingTest-fixed.zip

Note: The workaround only works on Visual Studio on Mac (Visual Studio 2019 on Windows still has a few bugs regarding xcframeworks)

@spouliot spouliot self-assigned this Mar 4, 2021
@spouliot spouliot added this to the Future milestone Mar 4, 2021
@spouliot
Copy link
Contributor

spouliot commented Mar 4, 2021

Thanks for the report. I'll have a look shortly...

Note: The workaround only works on Visual Studio on Mac (Visual Studio 2019 on Windows still has a few bugs regarding xcframeworks)

Please file separate issues for them and specify they are only happening on Windows. Thanks!

@spouliot spouliot added bug If an issue is a bug or a pull request a bug fix iOS Issues affecting Xamarin.iOS labels Mar 4, 2021
@leonluc-dev
Copy link
Author

leonluc-dev commented Mar 4, 2021

Thanks!

I've filed a separate issue for the Windows exclusive issues here: #10784

@spouliot
Copy link
Contributor

spouliot commented Mar 4, 2021

I've added an example project showcasing the issue below (using the Universal.xcframework that was supplied for xcframework testing).

@leonluc-dev just to be clear - the original attached project (in the main description) works without modification, right ?

  • the main app has the native reference
  • the binding project has no native reference

@leonluc-dev
Copy link
Author

leonluc-dev commented Mar 4, 2021

@spouliot The BindingTest.zip in the original post is the one showing the issue (xcframework is in the binding project, main app just has a reference to the binding project) and doesn't build properly.

The Bindingtest-fixed.zip in the later comment shows the temporary workaround which does build (xcframework in main app, binding project is just the C# api definition).

@spouliot
Copy link
Contributor

spouliot commented Mar 4, 2021

hmm, that's not what I downloaded...
but I could make the change (move native ref from app -> binding project) and see the build failure
investigating...

@spouliot
Copy link
Contributor

spouliot commented Mar 5, 2021

I'm hitting a localization issue (that I'll need to fix first, since it hides an issue) but I'm fairly sure the problem is (at least in part) because of the lack of

  <PropertyGroup>
    <NoBindingEmbedding>true</NoBindingEmbedding>
  </PropertyGroup>

inside your binding project, .csproj file.

In short, it's not really possible (at least it's a very bad idea [1]) to embed an .xcframework inside the assemblies. So we keep the native code in a "side car" with a manifest file that describe the arguments to be used. That is not new but, for compatibility, this was not the default for bindings. Now xcframework are new and makes this option even more important [2].

[1] is was already a bad idea for framework and even before, bitcode really made this a bad choice. The "cost" (in time) to extract the native code on each clean (not cached) build can be extremely large for some projects (larger than the rest of the build).

[2] it's even worse for .xcframework since assemblies are per-platforms (one for iOS, one for tvOS, one for watchOS, one for macOS...) and this would embed the native code for all platforms into each platform specific assemblies.

@leonluc-dev
Copy link
Author

leonluc-dev commented Mar 5, 2021

I see.

I'm not sure if I understand correctly, but the 'workaround' mentioned earlier (NativeReference to xcframework in app and just the C# binding definition in the binding project, combined with the NoBindingEmbedding option), is the supposed way to go for xcframeworks or even framework based libraries in general?

@spouliot
Copy link
Contributor

spouliot commented Mar 5, 2021

the 'workaround' mentioned earlier (NativeReference to xcframework in app

Not quite. The native reference should only be needed for the binding project(s) but, without <NoBindingEmbedding>true</NoBindingEmbedding>, the tooling cannot find the required manifest.

So

  • Apps does not require native reference(s) that are already added in bindings projects
  • Bindings to XCFrameworks requires the native reference(s)
  • Bindings to XCFrameworks requires <NoBindingEmbedding>true</NoBindingEmbedding>

and

  • The use of <NoBindingEmbedding>true</NoBindingEmbedding> is optional (for backward compatibility) for frameworks and (static .a) libraries binding projects. However build performance can suffer when the binaries are large (since it's costly to extract them after a clean operation).

@leonluc-dev
Copy link
Author

leonluc-dev commented Mar 5, 2021

Alright. Thanks for the information!

After setting the NoBindingEmbedding in the csproj file it seems to work properly now. The binding project generates a DLL and a .resource folder containing the xcframework and manifest and the app seems to build properly now.

In other contexts (direct distribution of the binary files or NuGet for example), would making sure the .resource folder is in the same folder as the referenced dll be enough to make it work?

@spouliot
Copy link
Contributor

spouliot commented Mar 5, 2021

Yes, the native bits are meant to be side-by-side (the "side car").

As for nuget it might be tricky since both nuget and xcframework are similar in design/goals but quite different in their implementation (nothing to do with NoBindingEmbedding). You find yourself with two standard ways of splitting code into different platforms.

Ideally you would want a single copy of any .xcframework inside the nuget (since they can be very large). I'm no expert in nuget, there might be ways to achieve this that I won't know about. Using symlinks seems an easy way... but those might not work properly on Windows (which is something else I'm not an expert on).

spouliot pushed a commit to spouliot/xamarin-macios that referenced this issue Mar 5, 2021
Code shared between msbuild and mtouch/mmp needs to share the
localization strings, otherwise the exception being thrown is of no help
to solve the issue.

THis happened if a wrong path was given for an `.xcframework` and
`FileCopier` reported an `MT1022` error.

Reference (not a fix for) xamarin#10774
spouliot added a commit that referenced this issue Mar 8, 2021
…mmp (#10799)

Code shared between msbuild and mtouch/mmp needs to share the
localization strings, otherwise the exception being thrown is of no help
to solve the issue.

THis happened if a wrong path was given for an `.xcframework` and
`FileCopier` reported an `MT1022` error.

Reference (not a fix for) #10774
@leonluc-dev
Copy link
Author

leonluc-dev commented Mar 8, 2021

After some experimenting it seems that simply referencing the binding project dll and placing the .resources folder in the same folder as the dll works, but only on Mac.
Windows based builds seemingly ignore any .resources folder in the same folder as the dll, leading to linker errors (possibly related to the same issue as reported in #10784?)

On Mac I am still working on getting this side-car setup to work in NuGet packages. Creating a NuGet package using the project pack command without a nuspec file only adds the dll to the package, so a custom nuget package needs to be made to add the .resources folder as well.

I've tried to add the .resources file to the lib/xamarinios10 folder and the content folder in the package but neither option allows the Xamarin tooling to 'find' the .resources folder once the package is added to a project.
Looking in the folder documentation for NuGet it seems the content folder would be the best way to add the resources folder, since it allows arbitrary content. But I'm having a hard time figuring out how to get the Xamarin tooling to 'pick it up'.

@spouliot spouliot added documentation The issue or pull request is about documentation and removed bug If an issue is a bug or a pull request a bug fix labels Mar 9, 2021
@spouliot
Copy link
Contributor

spouliot commented Mar 9, 2021

@leonluc-dev thanks for your investigations. I'm not an expert on nuget so I'll open a different issue to see how this should be handled.

Also please add any Windows-specific information inside #10784. I assumed the "side car" was working on Windows but I'm not fully sure anymore (it seems the cases I thought about actually did not use the feature).

I'll update the bindings documentation for "side car" and, once published, close this issue. Thanks!

@carlosmacmar
Copy link

the 'workaround' mentioned earlier (NativeReference to xcframework in app

Not quite. The native reference should only be needed for the binding project(s) but, without <NoBindingEmbedding>true</NoBindingEmbedding>, the tooling cannot find the required manifest.

So

  • Apps does not require native reference(s) that are already added in bindings projects
  • Bindings to XCFrameworks requires the native reference(s)
  • Bindings to XCFrameworks requires <NoBindingEmbedding>true</NoBindingEmbedding>

and

  • The use of <NoBindingEmbedding>true</NoBindingEmbedding> is optional (for backward compatibility) for frameworks and (static .a) libraries binding projects. However build performance can suffer when the binaries are large (since it's costly to extract them after a clean operation).

Thanks, this was really helpful, but in my own case, something was still missing and, even though the binding project built without errors, I was getting errors when trying to use the binding project (added inside references) in an app.

After some investigation, I've found out that when the XCFramework was added to the project, VS added it to .csproj file like this:

<NativeReference Include="..\..\MyLibrary.xcframework">
  <Kind>Static</Kind>
  <SmartLink>False</SmartLink>
</NativeReference>

I then realised this shouldn't be like this, because a XCFramework is not a static library (.a file). I changed it to:

<NativeReference Include="..\..\MyLibrary.xcframework">
  <Kind>Framework</Kind>
  <SmartLink>False</SmartLink>
</NativeReference>

And now it's working correctly without errors 🔥

I'll write a medium article about what I did and I'll add the link here as soon as I have it ready :)

@nguyenhuutinh
Copy link

nguyenhuutinh commented Apr 23, 2021

@carlosmacmar : how did you generate your metadata?

I tried with sharpie but it says error

fatal error:
      module 'DemoSDK' not found
@import DemoSDK.DNSResolver;
Binding...
  [write] ApiDefinitions.cs
  [write] StructsAndEnums.cs
1 error generated.

Thanks

@Atamanam
Copy link

Atamanam commented Oct 20, 2021

@leonluc-dev

  1. I have downloaded BindingTest-fixed.zip & also cannot figure out how this Apidefinition.cs links to the Native Reference [Universal] in the Binding Test App

namespace UniversalBinding
{
[BaseType(typeof(NSObject))]
interface FrameworkTest
{
[Export("func")]
int Func();
}
}

  1. I am unable to figure out how the native reference [Universal] is used in the Binding Test App.

@AmrAlSayed0
Copy link

AmrAlSayed0 commented Oct 25, 2021

For NuGet packages usually XamarinDownloadBuild is used to download and include the .xcframework inside the consuming project by a build and buildTransitive .targets file in the NuGet package like so https://github.com/xamarin/GoogleApisForiOSComponents/blob/main/source/Google/Analytics/Analytics.targets

jkasten2 added a commit to OneSignal/OneSignal-Xamarin-SDK that referenced this issue Nov 22, 2021
NoBindingEmbedding = true is required when using an XCFramework,
otherwise the comsuming app project will get linker errors on
Objective-C classes.

The details what NoBindingEmbedding does and why it is needed for
XCFrameworks is limited.
The source where this setting was found is below:
   - xamarin/xamarin-macios#10774 (comment)
@joemather
Copy link

How do we generate the metadata (ApiDefinition.cs) for an XCFramework?

Previously, we used sharpie, but sharpie seems incompatible with XCFrameworks.

I'm binding a framework with hundreds of APIs, so manually creating ApiDefinition.cs would be error prone.

@carlosmacmar - How did you generate it?

Thanks.

@4brunu
Copy link

4brunu commented Nov 30, 2021

@joemather You need point to the Framework inside the XCFramework.
Something like TwilioVoice.xcframework/ios-arm64/TwilioVoice.framework.
Here is an example
https://github.com/xamarin-bindings-for-twilio/TwilioVoiceXamarinIOS/blob/795bfc0d39dc70bd9cc064e77e9c5da50a64f6f0/src/bind.sh

@joemather
Copy link

@4brunu - That works. Thank you!

For other devs trying to generate metadata, here's the command that worked for me:

sharpie bind -o Metadata/MyFramework  \
             -namespace=MyFramework   \
             -sdk iphoneos15.0        \
             -framework ./Frameworks/MyFramework.xcframework/ios-arm64/MyFramework.framework     \
             -c -I ./Frameworks/Depedency1.xcframework/ios-arm64/Depedency1.framework/Headers    \
                -I ./Frameworks/Depedency2.xcframework/ios-arm64/Depedency2.framework/Headers

Also, this article explains how to fix "file not found" errors when sharpie fails to resolve #import statements in the headers.

lassana added a commit to cobrowseio/cobrowse-sdk-xamarin that referenced this issue Dec 19, 2021
"NoBindingEmbedding = True" is required when using an XCFramework, otherwise the comsuming app project will get linker errors on all Objective-C classes.
xamarin/xamarin-macios#10774 (comment)
@nrudnyk
Copy link

nrudnyk commented Mar 7, 2022

@leonluc-dev

On Mac I am still working on getting this side-car setup to work in NuGet packages. Creating a NuGet package using the project pack command without a nuspec file only adds the dll to the package, so a custom nuget package needs to be made to add the .resources folder as well.

have you been able to successfully setup NuGet Package? If so - could you please share some more info?

@leonluc-dev
Copy link
Author

@nrudnyk
I did. It requires a bit of extra work. You can find how to do it in this comment. With tips to improve the nuget packaging found here and here,

If you don't want to do all this manual configuration you can wait until .NET MAUI (along with .NET for iOS) releases later this year, which will support XCFrameworks in NuGet out of the box.

@quentinR0bert
Copy link

Do you have a link to a Documentation about the support of XCFrameworks into .NET MAUI ?

@nrudnyk
Copy link

nrudnyk commented Mar 8, 2022

@quentinR0bert Q2 of 2022

@nrudnyk
Copy link

nrudnyk commented Mar 8, 2022

@leonluc-dev I did exactly as described:

  • used NoBindingEmbedding
<PropertyGroup>
    <NoBindingEmbedding>true</NoBindingEmbedding>
 </PropertyGroup>
  • added .targets file and includ it in .csproj:
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
    <Target Name="BeforeCompile">
        <ItemGroup>
            <BindingResources Include="$(MSBuildThisFileDirectory)..\content\res\**\*.*" />
        </ItemGroup>
        <Copy SourceFiles="@(BindingResources)" DestinationFolder="$(TargetDir)\SciChart.iOS.Charting.resources\%(RecursiveDir)" ContinueOnError="false" />
        <ItemGroup>
            <NativeReference Include="$(TargetDir)\res\SciChart.xcframework">
                <Kind>Framework</Kind>
                <SmartLink>False</SmartLink>
            </NativeReference>
        </ItemGroup>
    </Target>
</Project>
  • and added files into my .nuspec (id is SciChart.iOS).
  <files>
    <file src="SciChart.iOS.Charting\bin\$configuration$\SciChart.iOS.Charting.dll" target="lib\Xamarin.iOS10\SciChart.iOS.Charting.dll" />
    <file src="SciChart.iOS.Charting\bin\$configuration$\SciChart.iOS.Charting.resources\**" target="content\res" />
    <file src="SciChart.iOS.Charting\SciChart.iOS.targets" target="build\SciChart.iOS.targets" />
  </files>

This results in Nuget Package which contains everything, but after adding it into the project, it copy files directly to the project, instead of NativeReference:
image
and since there's no native reference, I've got the following error:

/Library/Frameworks/Mono.framework/External/xbuild/Xamarin/iOS/Xamarin.Shared.targets(3,3): Error: /Users/nrudnyk/dev/scichart_root/SciChart.Xamarin.Examples/src/Xamarin.Examples.Demo.iOS/bin/iPhoneSimulator/Debug/res/SciChart.xcframework has an incorrect or unknown format and cannot be processed.
         (Xamarin.Examples.Demo.iOS)

what am I missing?

@leonluc-dev
Copy link
Author

leonluc-dev commented Mar 8, 2022

@nrudnyk
Your nuspec file and nobindingembedded setting are fine. The issue is in your targets file.

In your targets file (which is "run" just before compiling starts, as indicated at the top) you first copy the bindings resources from the NuGet package to $(TargetDir)\SciChart.iOS.Charting.resources\. This is fine, but then the targets file tries to include the XCframework to the build process as a NativeReference from $(TargetDir)\res\SciChart.xcframework, a different folder.

You need to make sure the nativereference you add during the build process (using the targets file) refers to the xcframework located in the folder you copied one step earlier.

As for why the files are added to the project as None references I'm not sure. I haven't seen that happen before.

@nrudnyk
Copy link

nrudnyk commented Mar 8, 2022

Thanks @leonluc-dev, I've updated .targets to the following:

    <Target Name="BeforeCompile">
        <ItemGroup>
            <BindingResources Include="$(MSBuildThisFileDirectory)..\content\res\**\*.*" />
        </ItemGroup>
        <Copy SourceFiles="@(BindingResources)" DestinationFolder="$(TargetDir)\res\%(RecursiveDir)" ContinueOnError="false" />
        <ItemGroup>
            <NativeReference Include="$(TargetDir)\res\SciChart.xcframework">
                <Kind>Framework</Kind>
                <SmartLink>False</SmartLink>
            </NativeReference>
        </ItemGroup>
    </Target>

and that does helped to fix the latter issue. Thanks a lot!

With that sorted out, I've faced another issue MTOUCH: Error MT2101: Can't resolve the reference 'System.IntPtr, locally while referencing bindings project by sample everything working fine, but when using Nuget Package is fails. That's whole another story. If there's a hint here - I'd appreciate it as well.

Another important thing, that adding package is taking very long time, it is adding file by file for each platform and it takes like 10-15 minutes easily... have anyone faced similar issue? what do you thing about this @rolfbjarne ?

Screen.Recording.2022-03-08.at.23.19.33.mov

@vas1l3nk
Copy link

vas1l3nk commented Mar 9, 2022

@4brunu Hello. How can I generate metadata via sharpie simultaneously for both ios-arm64 and ios-arm64_x86_64-simulator?

@4brunu
Copy link

4brunu commented Mar 9, 2022

You can't do both at once, as far as I know.
But usually they are the same, so that shouldn't be an issue, in most cases.
So what I do is to generate the metadata via sharpie only for ios-arm64.

@vas1l3nk
Copy link

vas1l3nk commented Mar 9, 2022

Thanks a lot for this information!

@Atamanam
Copy link

@leonluc-dev Can you please help me with documentation on how to integrate xcframework with a Xamarin iOS project by creating a binding project.

@lassana
Copy link

lassana commented Mar 11, 2022

FYI: instead of <Target Name="BeforeCompile"> you should use something like <Target Name="AddMyFrameworkWithUniqueName" BeforeTargets="BeforeCompile">, see #13603

@Atamanam
Copy link

@leonluc-dev Can you please help me with documentation on how to integrate xcframework with a Xamarin iOS project by creating a binding project.

@rolfbjarne
Copy link
Member

xcframeworks work in Xamarin.iOS projects now, so I don't think there's anything left to do here.

Also, in .NET, it's possible to run dotnet pack and we'll create a nupkg that will just work.

@ghost ghost locked as resolved and limited conversation to collaborators Nov 7, 2022
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
documentation The issue or pull request is about documentation iOS Issues affecting Xamarin.iOS
Projects
None yet
Development

No branches or pull requests