# Distributing your plugin

Where the host looks for plugins, how to ship signed builds, and how to debug a plugin that won't load.

## Install paths

CocoaPacketAnalyzer scans, in order:

1. **Built-in:** `CocoaPacketAnalyzer.app/Contents/PlugIns/` — bundled plugins that ship with the app.
2. **User:** `~/Library/Application Support/CocoaPacketAnalyzer/PlugIns/`
3. **Local:** `/Library/Application Support/CocoaPacketAnalyzer/PlugIns/`
4. **Network:** `/Network/Library/Application Support/CocoaPacketAnalyzer/PlugIns/`

The user-domain location is the right target for third-party plugins. A bundle is recognised by the `.cpaplugin` extension.

## Build settings checklist

Your plugin target must have:

| Setting | Value | Why |
|---------|-------|-----|
| `WRAPPER_EXTENSION` | `cpaplugin` | Recognised by the host scanner |
| Product type | `com.apple.product-type.bundle` | Loadable bundle, not a framework |
| `LD_RUNPATH_SEARCH_PATHS` | `@executable_path/../Frameworks @loader_path/../Frameworks` | Find host's dylibs at runtime |
| `MACOSX_DEPLOYMENT_TARGET` | `26.0` (or matching host) | Match host's deployment floor |
| Swift Concurrency | strict | Match host's strict concurrency mode |
| Embed CPAPluginSDK | **No** | Host provides the dylib |

## Two SDK consumption paths

The SDK can be consumed two ways. Both produce byte-identical plugin outputs.

### A. SPM path reference (in-repo)

If you've cloned the CPA repo (e.g. you're contributing built-in analyzers), reference the SDK as a local Swift package:

```
File > Add Packages... > Add Local... > select Packages/cpa_plugin_sdk
```

Add `CPAPluginSDK` to your plugin target's frameworks list. The 8 transitive frameworks are auto-resolved. See the in-tree example: `SamplePlugIn/NTPAnalyzer-XCF/`.

### B. XCFramework bundle (external)

If you're building a plugin outside the CPA repo, download the prebuilt SDK bundle. It contains 10 `.xcframework`s:

| Framework | Purpose |
|-----------|---------|
| `CPAPluginSDK.xcframework` | The umbrella you `import` |
| `CPADependencies.xcframework`, `CPAModels.xcframework`, `CPACommonTools.xcframework`, `CPALog.xcframework`, `CPAProtocolsStringsCache.xcframework`, `CPAKonstantsKeys.xcframework` | The Swift packages re-exported by the umbrella |
| `Injected.xcframework`, `cpa_objc_exception.xcframework` | Transitive dependencies the linker needs |
| `pcap.xcframework` | libpcap, used by `CPAModels` |

Drag them all into your project's Xcode navigator. Mark them all as **Linked, Do Not Embed** under your plugin target's *Frameworks and Libraries* section. The host CocoaPacketAnalyzer app provides the runtime dylibs.

See the canonical example: `SamplePlugIn/NTPAnalyzer-XCF/`.

> ✅ **Cross-Xcode-version stability.** The XCFramework distribution is built with library evolution enabled and ships `.swiftinterface` files for every module. You can build your plugin with a newer Xcode than the one that built the SDK — module ABI is stable.

## Code signing

For local development, "Sign to Run Locally" works. To distribute to other users:

- Sign with your Developer ID Application certificate.
- Notarize the `.cpaplugin` bundle (or the zip / DMG you ship).
- The host app must allow loading of plugins not signed by the same team — check CPA's hardened-runtime entitlements before assuming notarization alone is enough.

```bash
codesign --force --deep --sign "Developer ID Application: Your Name" \
    MyAnalyzer.cpaplugin
xcrun notarytool submit MyAnalyzer.cpaplugin.zip --keychain-profile MY_PROFILE --wait
```

## Debugging load failures

If your plugin doesn't appear in CPA's plugin list:

1. **Check the host log.** CPA emits load failures via `cpaLog` — open Console.app, filter by the CPA process, look for "PlugIns" messages.
2. **Verify `NSPrincipalClass`** is `<ModuleName>.<ClassName>`. A bare class name (no module) won't resolve from a Swift bundle.
3. **Check dyld errors** with `DYLD_PRINT_LIBRARIES=1`:
   ```bash
   DYLD_PRINT_LIBRARIES=1 /Applications/CocoaPacketAnalyzer.app/Contents/MacOS/CocoaPacketAnalyzer 2>&1 | grep -i myanalyzer
   ```
4. **Confirm rpath resolution**:
   ```bash
   otool -l MyAnalyzer.cpaplugin/Contents/MacOS/MyAnalyzer | grep -A2 LC_RPATH
   ```
   Should include `@executable_path/../Frameworks`.
5. **Confirm dylib references**:
   ```bash
   otool -L MyAnalyzer.cpaplugin/Contents/MacOS/MyAnalyzer
   ```
   Every `@rpath/libCPA*.dylib` line must resolve against the host's `Contents/Frameworks/`.

If a dylib in the host's Frameworks dir is older than the SDK your plugin was built against, the load may succeed but symbol lookups fail at first use. Build the plugin against the same SDK version that ships in the host.
