AgentSkillsCN

macios-nuget-packaging

将 .NET 绑定项目打包为 NuGet 包,以便分发。适用于需要将 xcframework 绑定封装进 NuGet 包,同时实现原生框架的正确嵌入、支持多 TFM(iOS + Mac Catalyst)、添加 NuGet 元数据与符号包,并发布至 NuGet.org 或私有仓库时使用。

SKILL.md
--- frontmatter
name: macios-nuget-packaging
description: Package .NET binding projects as NuGet packages for distribution. Use when you need to pack xcframework bindings into NuGet packages with proper native framework embedding, multi-TFM support (iOS + Mac Catalyst), NuGet metadata, symbol packages, and publishing to NuGet.org or private feeds.

NuGet Packaging for .NET Bindings

Package .NET binding projects as NuGet packages for distribution, with proper native framework embedding and multi-platform support.

When to Use This Skill

  • After binding projects build successfully and you need to distribute them
  • When creating NuGet packages that include native xcframeworks
  • When setting up multi-TFM packaging for iOS + Mac Catalyst
  • When configuring NuGet metadata, versioning, and publishing

Quick Start

1. Configure Package Metadata

Add NuGet metadata to the binding .csproj:

xml
<PropertyGroup>
  <PackageId>MyCompany.MyFramework.iOS</PackageId>
  <PackageVersion>1.0.0</PackageVersion>
  <Title>MyFramework for .NET iOS</Title>
  <Description>C# bindings for MyFramework xcframework</Description>
  <Authors>Your Name</Authors>
  <Copyright>Copyright © 2025 Your Company</Copyright>
  <PackageLicenseExpression>MIT</PackageLicenseExpression>
  <PackageProjectUrl>https://github.com/your-org/your-repo</PackageProjectUrl>
  <PackageIcon>icon.png</PackageIcon>
  <PackageReadmeFile>README.md</PackageReadmeFile>
  <PackageTags>ios;macos;maccatalyst;xamarin;dotnet;bindings</PackageTags>
  <RepositoryUrl>https://github.com/your-org/your-repo</RepositoryUrl>
  <RepositoryType>git</RepositoryType>
</PropertyGroup>

2. Configure Native Framework Embedding

By default, binding projects embed the native framework in the NuGet. This is the recommended approach for distribution:

xml
<PropertyGroup>
  <!-- Default: true — framework is embedded in the NuGet package -->
  <!-- Set to false only for local development with project references -->
  <NoBindingEmbedding>false</NoBindingEmbedding>
</PropertyGroup>

<ItemGroup>
  <NativeReference Include="../Frameworks/MyFramework.xcframework">
    <Kind>Framework</Kind>
    <SmartLink>True</SmartLink>
  </NativeReference>
</ItemGroup>

Embedding behavior:

NoBindingEmbeddingNuGet contains native framework?App must add NativeReference?
false (default)✅ Yes — self-contained NuGetNo
true❌ No — smaller NuGetYes — app must reference xcframework

For NuGet distribution, always use NoBindingEmbedding=false (the default) so consumers don't need the xcframework separately.

3. Multi-TFM Configuration

Target both iOS and Mac Catalyst:

xml
<PropertyGroup>
  <TargetFrameworks>net10.0-ios;net10.0-maccatalyst</TargetFrameworks>
  <!-- Or include net9.0 for backward compatibility -->
  <TargetFrameworks>net10.0-ios;net10.0-maccatalyst;net9.0-ios;net9.0-maccatalyst</TargetFrameworks>
</PropertyGroup>

The NuGet will contain platform-specific assemblies under:

code
lib/
├── net10.0-ios/
│   └── MyFramework.Binding.dll
├── net10.0-maccatalyst/
│   └── MyFramework.Binding.dll
├── net10.0-ios/
│   └── MyFramework.Binding.dll
└── net10.0-maccatalyst/
    └── MyFramework.Binding.dll

4. Pack the NuGet

bash
# Pack in Release mode
dotnet pack MyFramework.Binding/MyFramework.Binding.csproj -c Release

# Output: MyFramework.Binding/bin/Release/MyCompany.MyFramework.iOS.1.0.0.nupkg

5. Verify the Package

bash
# List package contents
dotnet nuget locals global-packages -l
unzip -l MyFramework.Binding/bin/Release/MyCompany.MyFramework.iOS.1.0.0.nupkg

# Or use NuGet Package Explorer (GUI)

Verify the package contains:

  • lib/net10.0-ios/MyFramework.Binding.dll (and maccatalyst)
  • Native framework files (if embedding)
  • Package metadata files

6. Publish

bash
# Push to NuGet.org
dotnet nuget push MyFramework.Binding/bin/Release/MyCompany.MyFramework.iOS.1.0.0.nupkg \
  --api-key YOUR_API_KEY \
  --source https://api.nuget.org/v3/index.json

# Push to a private feed
dotnet nuget push *.nupkg \
  --api-key YOUR_API_KEY \
  --source https://pkgs.dev.azure.com/your-org/_packaging/your-feed/nuget/v3/index.json

Complete .csproj Template

xml
<Project Sdk="Microsoft.NET.Sdk">
  <PropertyGroup>
    <TargetFrameworks>net10.0-ios;net10.0-maccatalyst</TargetFrameworks>
    <Nullable>enable</Nullable>
    <ImplicitUsings>true</ImplicitUsings>
    <IsBindingProject>true</IsBindingProject>

    <!-- NuGet metadata -->
    <PackageId>MyCompany.MyFramework.iOS</PackageId>
    <PackageVersion>1.0.0</PackageVersion>
    <Title>MyFramework .NET Bindings</Title>
    <Description>C# bindings for MyFramework native xcframework</Description>
    <Authors>Your Name</Authors>
    <Copyright>Copyright © 2025 Your Company</Copyright>
    <PackageLicenseExpression>MIT</PackageLicenseExpression>
    <PackageProjectUrl>https://github.com/your-org/your-repo</PackageProjectUrl>
    <PackageReadmeFile>README.md</PackageReadmeFile>
    <PackageTags>ios;maccatalyst;dotnet;bindings;myframework</PackageTags>
    <RepositoryUrl>https://github.com/your-org/your-repo</RepositoryUrl>
    <RepositoryType>git</RepositoryType>

    <!-- Generate XML documentation -->
    <GenerateDocumentationFile>true</GenerateDocumentationFile>
  </PropertyGroup>

  <!-- Native framework reference -->
  <ItemGroup>
    <NativeReference Include="../Frameworks/MyFramework.xcframework">
      <Kind>Framework</Kind>
      <SmartLink>True</SmartLink>
    </NativeReference>
  </ItemGroup>

  <!-- Package additional files -->
  <ItemGroup>
    <None Include="README.md" Pack="true" PackagePath="" />
    <None Include="icon.png" Pack="true" PackagePath="" />
  </ItemGroup>

  <!-- Binding source files -->
  <ItemGroup>
    <ObjcBindingApiDefinition Include="ApiDefinition.cs" />
    <ObjcBindingCoreSource Include="StructsAndEnums.cs" />
  </ItemGroup>

  <!-- Dependencies on other binding packages -->
  <ItemGroup>
    <PackageReference Include="MyCompany.OtherFramework.iOS" Version="1.0.0" />
  </ItemGroup>
</Project>

Multi-Framework SDK Packaging

When packaging a multi-framework SDK (like Facebook SDK), you have two options:

Option A: One NuGet per Framework (Recommended)

Each framework gets its own NuGet with dependency references:

code
MyCompany.SDK.Core.iOS          (1.0.0)
MyCompany.SDK.Login.iOS         (1.0.0) → depends on Core
MyCompany.SDK.Share.iOS         (1.0.0) → depends on Core
MyCompany.SDK.Gaming.iOS        (1.0.0) → depends on Core + Share

In the Login binding .csproj:

xml
<ItemGroup>
  <ProjectReference Include="../Core.Binding/Core.Binding.csproj" />
</ItemGroup>

When packing, NuGet automatically converts ProjectReference to package dependencies.

Option B: Meta-Package

Create a meta-package that pulls in all framework NuGets:

xml
<!-- MyCompany.SDK.iOS.csproj -->
<Project Sdk="Microsoft.NET.Sdk">
  <PropertyGroup>
    <TargetFrameworks>net10.0-ios;net10.0-maccatalyst</TargetFrameworks>
    <PackageId>MyCompany.SDK.iOS</PackageId>
    <PackageVersion>1.0.0</PackageVersion>
    <Description>Complete SDK — includes Core, Login, Share, and Gaming</Description>
    <!-- No binding files — this is a dependency-only package -->
    <IncludeBuildOutput>false</IncludeBuildOutput>
  </PropertyGroup>

  <ItemGroup>
    <PackageReference Include="MyCompany.SDK.Core.iOS" Version="1.0.0" />
    <PackageReference Include="MyCompany.SDK.Login.iOS" Version="1.0.0" />
    <PackageReference Include="MyCompany.SDK.Share.iOS" Version="1.0.0" />
    <PackageReference Include="MyCompany.SDK.Gaming.iOS" Version="1.0.0" />
  </ItemGroup>
</Project>

Versioning Strategy

Semantic Versioning

Follow the native SDK version when possible:

code
Native SDK v18.0.2  →  NuGet 18.0.2
Native SDK v18.1.0  →  NuGet 18.1.0

If your bindings have their own fixes independent of the native SDK:

code
Native SDK v18.0.2, binding fix #1  →  NuGet 18.0.2.1
Native SDK v18.0.2, binding fix #2  →  NuGet 18.0.2.2

Pre-release Versions

xml
<PackageVersion>18.0.2-preview.1</PackageVersion>  <!-- Pre-release -->
<PackageVersion>18.0.2-beta.1</PackageVersion>      <!-- Beta -->
<PackageVersion>18.0.2</PackageVersion>              <!-- Stable -->

Package README Template

Include a README.md in the NuGet:

markdown
# MyFramework .NET Bindings

C# bindings for the MyFramework native iOS/Mac Catalyst xcframework.

## Installation

```bash
dotnet add package MyCompany.MyFramework.iOS

Usage

csharp
using MyFramework;

var manager = MyManager.SharedInstance;
manager.Configure(new MyConfiguration("app-id", "app-secret"));

Requirements

  • .NET 10.0+ (iOS / Mac Catalyst)
  • iOS 15.0+ / macOS 12.0+

Native SDK Version

This package includes MyFramework native SDK v18.0.2.

code

## CI/CD Pipeline

### GitHub Actions Workflow

```yaml
name: Pack and Publish

on:
  push:
    tags: ['v*']

jobs:
  pack:
    runs-on: macos-latest
    steps:
    - uses: actions/checkout@v4

    - name: Setup .NET
      uses: actions/setup-dotnet@v4
      with:
        dotnet-version: '10.0.x'

    - name: Install workloads
      run: |
        dotnet workload install ios
        dotnet workload install maccatalyst

    - name: Build
      run: dotnet build -c Release

    - name: Pack
      run: dotnet pack -c Release --no-build

    - name: Push to NuGet
      run: |
        dotnet nuget push **/*.nupkg \
          --api-key ${{ secrets.NUGET_API_KEY }} \
          --source https://api.nuget.org/v3/index.json \
          --skip-duplicate

Troubleshooting

Package Too Large

If the NuGet is very large (100MB+) because of embedded xcframeworks:

  • Ensure SmartLink=True to strip unused architectures
  • Consider shipping separate packages per framework instead of one mega-package
  • Use NativeReference with IsCxx=False if C++ runtime isn't needed

Native Framework Not Found at Runtime

Consumer gets linker errors despite installing the NuGet:

  • Verify NoBindingEmbedding is false (default) for published packages
  • Check that the NativeReference Include path is correct relative to the .csproj
  • Ensure the xcframework contains the needed slice (ios-arm64, maccatalyst-arm64_x86_64)

Missing Dependencies Between Packages

If Framework B depends on Framework A:

  • The NuGet for B must have a <PackageReference> to A's NuGet
  • Or use <ProjectReference> during development (auto-converted to package dep when packing)

Symbol Package

For debugging support, generate a symbol package:

xml
<PropertyGroup>
  <IncludeSymbols>true</IncludeSymbols>
  <SymbolPackageFormat>snupkg</SymbolPackageFormat>
</PropertyGroup>

Push alongside the main package:

bash
dotnet nuget push *.nupkg --source https://api.nuget.org/v3/index.json
# .snupkg is pushed automatically to the NuGet symbol server

References