title: macOS 插件实现与打包 date: 2026-08-27 tags:

  • AiPPT
  • macOS
  • PowerPoint
  • PPAM
  • Swift
  • PKG status: active

macOS 插件实现与打包

快速打包

WebView前置条件

sign_and_notarize.command只消费已经同步到macOS仓库的离线页面,不会构建前端或解压offline.zip。WebView有改动时,先执行WebView实现与打包 > 快速打包,再清空并解压到:

App/macos-app/src/offline/

交互打包:

./AiPPT-pkg/AiPPTAaddin/Scripts/sign_and_notarize.command

脚本询问版本、渠道、架构、签名、公证Profile和Git发布参数。

正式非交互发布:

./AiPPT-pkg/AiPPTAaddin/Scripts/sign_and_notarize.command \
  --version 2.0.3 \
  --source PLUGIN \
  --archs "arm64 x86_64" \
  --notary-profile aippt-profile \
  --yes

正式流程编译Universal Binary,签名App和PKG,提交Apple公证并Staple,然后提交版本、创建annotated Tag并原子推送分支和Tag。

只生成本地测试包:

./AiPPT-pkg/AiPPTAaddin/Scripts/sign_and_notarize.command \
  --version 2.0.3 \
  --source PLUGIN \
  --archs "arm64 x86_64" \
  --skip-notarization \
  --no-git-release \
  --yes

默认产物:

AiPPT-pkg/AiPPTAaddin/AiPPT 插件-<version>.pkg

正式发布要求Developer ID Application、Developer ID Installer和notarytool Keychain Profile可用。

实现概览

macOS端由四个产物组成:

产物作用
AiPPT.ppamPowerPoint启动加载项,提供Ribbon和VBA入口
AiPPTAddin.bundleSwift dylib,处理窗口、网络和PPT操作
AiPPT.app独立桌面程序,提供工作台和PPAM修复安装
AiPPT 插件-<version>.pkg安装以上文件的分发包

运行链路:

PowerPoint加载AiPPT.ppam
→ VBA调用Swift dylib导出函数
→ rootRouter分发Native方法
→ Swift操作PowerPoint或打开NSPanel
→ WKWebView加载共享前端页面

仓库:

aippt-macos-addin

1. AiPPT.ppam

源文件:

AiPPTAddin/AiPPT.pptm

分发文件:

AiPPT.ppam

安装位置:

~/Library/Group Containers/UBF8T346G9.Office/
User Content.localized/Startup.localized/PowerPoint/AiPPT.ppam

PowerPoint启动时自动加载该文件。PPAM内的VBA负责:

  • Ribbon回调;
  • 调用Swift dylib;
  • 传递UTF-16字符串和JSON参数;
  • 接收Native返回值。

VBA依赖固定的dylib路径:

/Library/Application Support/Microsoft/
AiPPTAddin.bundle/Contents/MacOS/AiPPTAddin

修改导出函数名、参数格式或安装路径时,VBA和Swift必须一起改。

2. AiPPTAddin.bundle

工程:

AiPPTAddin/AiPPTAddin.xcodeproj

主二进制是Swift dylib,放在:

AiPPTAddin.bundle/Contents/MacOS/AiPPTAddin

VBA入口

AiPPTAddin/AiPPTBridge/Globals.swift

主要导出:

导出函数用途
initRibbonConfig初始化Ribbon配置、离线资源和Client
executeSwiftMessageVBA到Swift的统一消息入口
getImageSize图片尺寸
getImageFromApp下载或读取图标
freeUTF16Pointer释放返回字符串
getContextMenuVisible右键菜单可见性

业务路由:

executeSwiftMessage(method, params)
→ rootRouter
→ Native方法
→ JSON结果
→ UTF-16返回VBA

Native实现

AiPPTAddin/Native/

包含:

  • Application、Slides和Shapes;
  • Browser和窗口;
  • Libraries;
  • UniformFont/UniformColor;
  • ShapeConnector和DesignTools;
  • 导出PDF、视频和长图;
  • PPT拼图;
  • 文件和系统设置。

窗口

AppDelegate.createWindow
→ NSPanel
→ NSHostingView
→ WebBrowserView
→ WKWebView

NSPanel按业务ID复用。它是浮动窗口,不是PowerPoint原生停靠TaskPane。

3. AiPPT.app

工程:

App/macos-app/src/AiPPT.xcworkspace

安装名称:

/Applications/AiPPT插件.app

职责:

  • 独立AiPPT工作台;
  • 素材库;
  • AppleScript驱动PowerPoint;
  • 检查并修复PPAM安装;
  • 本地文件和设置;
  • StoreKit内购代码骨架。

PPAM修复安装:

OfficePowerPointStartupFolderAccess
→ NSOpenPanel获取Startup目录授权
→ 保存security-scoped bookmark
→ 复制App资源中的AiPPT.ppam

StoreKit有商品查询和购买队列代码,但收据校验和权益发放没有完成。状态按“有代码,未完成交易闭环”处理。

4. 代码保护状态

macOS端没有使用.NET Reactor、UPX或其他专用混淆器。

产物当前保护
AiPPTAddin.bundleSwift原生编译、Release -O和whole-module optimization
AiPPT.appSwift原生编译、Release -O和dead code stripping
Web离线资源Webpack压缩和变量名缩短
PKGDeveloper ID签名、Apple公证和Staple

这些能力不等于代码混淆。当前Add-in工程仍配置:

COPY_PHASE_STRIP = NO
DEAD_CODE_STRIPPING = NO
GCC_SYMBOLS_PRIVATE_EXTERN = NO

因此Release dylib可能保留较多符号。增加符号裁剪时必须保留:

@_silgen_name导出函数
VBA/Swift Bridge入口
Objective-C运行时方法
WKWebView消息处理器
AppleScript调用入口

错误裁剪会导致PPAM加载成功但VBA无法调用Swift实现。

5. 开发依赖

依赖用途
Xcode编译dylib和App
PowerPoint macOS宿主验证
CocoaPodsApp工程依赖安装
Swift Package ManagerSwift运行依赖
Packages/packagesbuild生成PKG
Developer ID Application签bundle、dylib和App
Developer ID Installer签PKG
notarytoolApple公证
WKWebView离线资源插件页面

主要SPM依赖:

SwifterSwift
SwiftyJSON
ZIPFoundation
SDWebImageSwiftUI
Async
swift-collections

CocoaPods中的SwiftLint和SwiftFormat属于开发工具。

6. 构建和组装

WebView离线资源

输入目录:

App/macos-app/src/offline/

完整链路:

aippt-frontend-addin的dist/offline.zip
→ 人工清空并解压到App/macos-app/src/offline
→ Xcode rsync到AiPPT.app/Contents/Resources/offline
→ sign_and_notarize.command复制到AiPPTAddin.bundle/Contents/Resources/offline
→ Packages Payload同时包含AiPPT.app和AiPPTAddin.bundle
→ PKG安装

安装后有两条加载路径:

使用方离线页面位置加载方式
PowerPoint插件PowerPoint沙盒内Application Support/AiPPT/offline/Swift把offline/<page>.html解析到home目录,WKWebView调用loadFileURL
AiPPT插件.appApp自身Contents/Resources/offline/Bundle.main加载offline/desktop.html

postinstall把bundle资源中的offline/同步到:

~/Library/Containers/com.microsoft.Powerpoint/Data/
Library/Application Support/AiPPT/offline/

运行时还会检查服务端offline包版本;有更新时下载offline.zip并解压到home目录。

人工边界只有第一步:前端offline.zip同步到App/macos-app/src/offline/。后续Xcode、Payload组装、PKG安装和WKWebView加载均自动完成。

当前缺失处理存在风险:

  • App/macos-app/src/offline/不存在时,打包脚本跳过复制,不会失败;
  • postinstall找不到offline目录时只记录警告,不会让安装失败;
  • 正式发布前必须检查bundle和App中都存在offline/desktop.html及业务HTML。

编译Swift dylib

xcodebuild AiPPTAddin
configuration=Release
platform=macOS
archs=arm64 x86_64

编译AiPPT.app

pod install --deployment
xcodebuild AiPPT.app
MARKETING_VERSION=<version>
archs=arm64 x86_64

组装Payload

Payload/
├── AiPPTAddin.bundle/
│   ├── Contents/MacOS/AiPPTAddin
│   ├── Contents/Info.plist
│   └── Contents/Resources/
│       ├── offline/
│       ├── AiPPT.ppam
│       └── 模板PPTX
└── AiPPT.app/
    └── Contents/Resources/AiPPT.ppam

打包资源:

AiPPT.ppam
Standard.pptx
SingleStandardIconBlack.pptx
AiPPTPluginOverview.pptx
offline/
Resources/

7. 签名

顺序:

签AiPPTAddin主二进制
→ 签AiPPTAddin.bundle
→ 签AiPPT.app
→ codesign --verify
→ packagesbuild
→ productsign签PKG
→ pkgutil --check-signature

证书:

证书对象
Developer ID Applicationdylib、bundle、App
Developer ID InstallerPKG

AiPPT.app签名使用AiPPT.entitlements

8. 公证

xcrun notarytool submit <pkg> --keychain-profile <profile> --wait
→ xcrun stapler staple <pkg>
→ xcrun stapler validate <pkg>
→ spctl -a -t install -vv <pkg>

通过标准:

notarytool status = Accepted
stapler validate成功
spctl source = Notarized Developer ID

9. PKG结构

Packages工程:

AiPPT-pkg/AiPPTAaddin/AiPPTAddin.pkgproj

初始安装位置:

/Library/Application Support/AiPPT

PKG包含:

AiPPTAddin.bundle
AiPPT.app
preinstall
postinstall

10. preInstall和postinstall

preInstall

删除旧Payload中的bundle和App,处理文件flags、权限和隔离属性。

postinstall

顺序:

  1. 识别当前控制台用户;
  2. 关闭PowerPoint;
  3. 复制PPAM到PowerPoint Startup目录;
  4. 复制模板和离线资源到PowerPoint容器;
  5. 原子替换AiPPTAddin.bundle
  6. 安装AiPPT插件.app
  7. 写入版本和渠道设置;
  8. 启动PowerPoint和App;
  9. 上报install_success

日志:

/tmp/aippt-postinstall.log

关键安装位置:

文件位置
PPAMPowerPoint Startup目录
dylib bundle/Library/Application Support/Microsoft/AiPPTAddin.bundle
桌面App/Applications/AiPPT插件.app
离线资源PowerPoint沙盒容器内的AiPPT目录

11. 发布产物

AiPPT 插件-<version>.pkg

发布前检查:

  • App和dylib都是arm64 x86_64
  • codesign --verify --deep --strict通过;
  • PKG使用Developer ID Installer签名;
  • Apple公证Accepted;
  • Staple有效;
  • AiPPT.app/Contents/Resources/offline/desktop.html存在;
  • AiPPTAddin.bundle/Contents/Resources/offline/包含业务HTML;
  • 安装后PowerPoint沙盒的Application Support/AiPPT/offline/已同步;
  • 安装后PowerPoint能自动加载PPAM;
  • VBA能找到固定路径下的dylib;
  • NSPanel能打开在线和离线页面;
  • App能修复PPAM。

12. 已知约束

  • VBA和Swift的ABI契约只体现在导出函数名、UTF-16和JSON格式中;
  • dylib安装路径被VBA引用,不能单边修改;
  • PPAM受PowerPoint宏安全策略影响;
  • codesign --deep应谨慎使用,嵌套代码最好逐层签名;
  • notarytool依赖本机Keychain profile;
  • Packages工程格式可能受Packages版本影响;
  • App Store内购未形成交易闭环。

13. 相关文档