跳到主要内容
知仓学习社ZHICANG

build-ios-binary

Compile a PAM control's iOS Obj-C/Swift module into the FLAT device-slice `.framework` for a `.ppmplugin` bundle (never an `.xcframework` — the wrap…

写文件严重 0 · 高危 1microsoft/power-platform-skills

它会碰到什么

扫了多少1 个文本文件,15 KB
它会碰到什么写文件
命中总数1 处
命中统计严重 0 · 高 1 · 中 0 · 低 0
逐条看命中(1 条严重或高危)
  • SKILL.md:127fs-destructive
    rm -rf ../ios/<Pascal>Plugin.framework

这一栏是扫描器报的事实,不是结论。命中多不等于有毒(安全工具、规则库、示例脚本本来就会包含危险写法),命中少也不等于干净。它和你手上的凭据、文件、网络有什么关系,需要你自己看。

技能内容

/build-ios-binary

Turns the extension's iOS source (ios/RCT<Pascal>Module.{h,m}) into the flat <Pascal>Plugin.framework/ (device slice) that the wrap runtime loads. iOS analogue of [/build-android-binary](../build-android-binary/SKILL.md): source in, a prebuilt framework out.

> Ship a FLAT .framework, NOT an .xcframework. The wrap pipeline expects ios/<Name>.framework and does not descend into an .xcframework — shipping one fails with "Framework '<Name>.framework' not found in plugin." This skill builds the device archive only and copies out the flat .framework. See [ppmplugin-format §5b](../../shared/ppmplugin-format.md).

> Mac-only. Requires Xcode. On Linux/Windows this skill BLOCKs — Android-only contributors can't produce the iOS slice.

>

> KNOWN LIMITATION — read this. The single hard part is React-Core: the framework compiles against React's headers (#import <React/RCTBridgeModule.h>) from the control repo's own pinned react-native devDep, while never embedding React — the wrap host provides it at runtime (weak-link). The coupling that matters is the RN version pin (0.79.7): it must match the RN the wrap host ships. The exact weak-link flags / header-search-path setup aren't yet validated against a live wrap host — that's the main risk area. React symbol/header errors mean the RN pin diverged from the host's RN, not a code bug.

Read [shared/ppmplugin-format.md](../../shared/ppmplugin-format.md) §5b (iOS binary requirements) and the [naming-conventions.md](../../shared/naming-conventions.md) iOS rows.

What this skill does NOT do

  • Does not author manifest.json (run [/generate-ppmplugin-manifest](../generate-ppmplugin-manifest/SKILL.md) first — this skill reads it for cross-checks and the framework/moduleClass names).
  • Does not zip the .ppmplugin — that's [/assemble-ppmplugin](../assemble-ppmplugin/SKILL.md).
  • Does not build Android — that's /build-android-binary.
  • Does not modify the canonical ios/ or the podspec — all adjustments live in a throwaway copy under ppmplugin/staging/ios-build/.
  • Does not sign the framework — the wrap pipeline signs at packaging time (given BUILD_LIBRARY_FOR_DISTRIBUTION=YES SKIP_INSTALL=NO). No signing skill needed.

Step 1 — Read shared docs + prereq block

  1. Read [shared/shared-instructions.md](../../shared/shared-instructions.md) and [shared/ppmplugin-format.md](../../shared/ppmplugin-format.md).
  2. Read ppmplugin/staging/manifest.json. If absent, STOP with NEEDS_CONTEXT: manifest.json missing — run /generate-ppmplugin-manifest first. If it has no entrypoints.ios, STOP with NEEDS_CONTEXT: manifest targets Android-only — re-run /generate-ppmplugin-manifest and choose iOS/Both.
  3. Read the ## ppmplugin (third-party controls) block in .extension-state.md. Replace-existing gate: if ppmplugin/staging/ios/<framework>.framework already exists, surface it (with its build timestamp) and ask via AskUserQuestionKeep existing (skip to Step 6) vs Replace (rebuild). Default Keep if source + manifest unchanged since the recorded build, else Replace (note Keep is stale). Per [ppmplugin-format §1](../../shared/ppmplugin-format.md) replace-existing rule.
  4. Run prereq checks and print the visible block (shared-instructions §9.2). Policy: resolve, don't punt (locate tools by path; offer + run safe installs):

| Check | Verify | Auto-fix |

|---|---|---|

| macOS | uname = Darwin | NOT fixable — STOP with BLOCKED: /build-ios-binary is Mac-only (iOS needs Xcode) on Linux/Windows |

| Xcode 16+ (26.2+ recommended) | xcodebuild -version | NOT auto-fixable — print: install Xcode from the App Store / xcodes; STOP if missing |

| React-Core headers present | node_modules/react-native/React/Base/RCTBridgeModule.h exists (the react-native devDep, in the control repo) | if the devDep isn't installed: pnpm install in the repo |

| CocoaPods (optional — fallback path only) | pod --version | Not required for the recommended header-only build (Step 2). Only needed if you fall back to the Podfile path; brew install cocoapods on confirm. |


Step 2 — React-Core source (self-contained) + confirm the config

The framework needs React's headers at compile time. The source is the control repo's own node_modules/react-native — the pinned react-native devDep (0.79.7 for pen-input), which ships the React headers (React/Base/RCTBridgeModule.h). The build is fully self-contained in the control repo. If the devDep isn't installed, run pnpm install; only if it's genuinely absent, ask the user for a react-native path.

Recommended: header-only React (no CocoaPods). Aggregate the React headers into a flat include dir in the staging copy and point the framework target at it — cleaner and faster than the Pod chain, and it avoids the CocoaPods/React-Codegen failures that bite under recent Xcode (see [ppmplugin-format §5b](../../shared/ppmplugin-format.md)):

mkdir -p ppmplugin/staging/ios-build/include/React
# copy the RN headers the module imports (Base + the Libraries it uses):
cp node_modules/react-native/React/Base/*.h        ppmplugin/staging/ios-build/include/React/
cp -R node_modules/react-native/React/Base node_modules/react-native/Libraries  ppmplugin/staging/ios-build/include/React/ 2>/dev/null || true

Then set on the framework target: HEADER_SEARCH_PATHS = $(inherited) $(SRCROOT)/include and OTHER_LDFLAGS = -undefined dynamic_lookup (weak-links React's symbols — the host provides them at runtime). -undefined dynamic_lookup is deprecated by Apple (works today, may break in a future Xcode — known caveat).

Fallback: CocoaPods — only if the header-only path doesn't suffice. A Podfile referencing the devDep (pod 'React-Core', :path => '<repo>/node_modules/react-native') gives CocoaPods the header map, but on recent Xcode/RN-0.79 you'll need post_install patches (boost URL, Yoga _pt, RCT-Folly clockid_t, boost std::unary_function) and may still hit the React-Codegen sandbox failure — all documented in ppmplugin-format §5b.

What matters either way is that the pinned RN version (0.79.7) matches the RN the wrap host ships at runtime (React is weak-linked, host-provided). Print the detected RN version and ask via AskUserQuestion to confirm / override the source + version before building. State plainly: "React is provided by the wrap host at runtime; React symbol/header errors at build or load mean the RN pin has diverged from the host's RN version."


Step 3 — Stage a standalone Xcode framework project (canonical ios/ stays pristine)

Like the Android build, work from a throwaway copy so the source the engineer maintains is never touched.

  1. Copy ios/*.{h,m}ppmplugin/staging/ios-build/Sources/ (fresh each run).
  2. Generate the umbrella header ppmplugin/staging/ios-build/<Pascal>Plugin.h and mark it a Public header (build hygiene, not runtime-required — the wrap player dlopens the binary and uses NSClassFromString, it never imports the module, so the framework loads fine without a module map; the umbrella just silences the DEFINES_MODULE warning. Generate it anyway for a clean build). Minimal contents:
   #import <Foundation/Foundation.h>
   FOUNDATION_EXPORT double <Pascal>PluginVersionNumber;
   FOUNDATION_EXPORT const unsigned char <Pascal>PluginVersionString[];
  1. Generate a standalone Xcode framework project ppmplugin/staging/ios-build/<Pascal>Plugin.xcodeproj, product/target name <Pascal>Plugin (= entrypoints.ios.framework), compiling the copied sources, with the umbrella as the Public header. Derive from the canonical podspec:
  • System frameworks to link: from the podspec s.frameworks (e.g. pen-input → PencilKit, UIKit, CoreGraphics, QuartzCore).
  • Deployment target: from s.platform (e.g. :ios, "14.0") — but floor it at the supported PAM shell minimum iOS 16.0.
  • xcodeproj-gem trap (if generating via the xcodeproj gem): when a group already carries its path (e.g. Sources), add file refs filename-onlynew_group('Sources','Sources') + new_reference("Sources/<file>") doubles to Sources/Sources/<file> → file-not-found. Pin a known-good gen_project.rb rather than re-deriving it each run.
  1. Set the build settings (each maps to a documented wrap/on-device failure — see [ppmplugin-format §5b](../../shared/ppmplugin-format.md)):

| Setting | Value |

|---|---|

| Mach-O Type | Dynamic Library (NOT Static) |

| Dead Code Stripping | NO (preserves the +moduleName class method and RCT_EXPORT_METHOD metadata the host reads after dlopen) |

| Defines Module | YES |

| Enable Bitcode | NO |

| HEADER_SEARCH_PATHS | $(inherited) $(SRCROOT)/include (header-only React from Step 2) |

| OTHER_LDFLAGS | -undefined dynamic_lookup (weak-link React; host-provided at runtime) |

| BUILD_LIBRARY_FOR_DISTRIBUTION | YES |

| SKIP_INSTALL | NO |

| GENERATE_INFOPLIST_FILE | YES (REQUIRED — a framework with no Info.plist is rejected by the wrap codesign step) |

| INFOPLIST_KEY_CFBundleDisplayName | <Pascal>Plugin |

| MARKETING_VERSION | <version from manifest> |

| CURRENT_PROJECT_VERSION | 1 |

| PRODUCT_BUNDLE_IDENTIFIER | com.powerapps.<lowername> |

The canonical ios/ + podspec are never modified — report the staging adjustments applied.


Step 4 — Assert manifest conformance

Before building, verify the source matches the manifest (the iOS analogue of the Android DexClassLoader asserts):

  • Class name in ios/RCT<Pascal>Module.h == manifest.entrypoints.ios.moduleClass.
  • Class name in the .m == manifest.entrypoints.ios.moduleClass, and + (NSString *)moduleName returns manifest.receivers[].nativeModule.
  • Each RCT_EXPORT_METHOD(<name>:…) name is on manifest.receivers[].methods.
  • The module class instantiates via no-arg [cls new] — the runtime resolves moduleClass after loading the framework and constructs it with [cls new]. A custom designated initializer that takes arguments breaks instantiation → the plugin is skipped at load. If the .m declares an - (instancetype)initWith… and no plain init, STOP with BLOCKED: <moduleClass> must support no-arg initialization.
  • **Each RCT_EXPORT_METHOD takes one NSDictionary request first param* (then resolver/rejecter) — the wrap proxy spreads the PCF's args: [request] positionally. A method expanding the request into multiple positional params won't receive its data; surface a WARNING (a no-arg op like getStatus taking only resolver/rejecter is fine — [ppmplugin-format §2](../../shared/ppmplugin-format.md)).

A mismatch means the manifest and the binary disagree — the call won't reach the module on device. STOP with the specific mismatch rather than building a broken pair.


Step 5 — Build the flat .framework (device slice only)

Run for real; surface the artifact path, not the streaming log. Archive the DEVICE slice only (the wrap CI wants a flat <Name>.framework, not an .xcframework — see §5b), then copy the framework straight out. No -create-xcframework step.

cd ppmplugin/staging/ios-build
# device archive only:
xcodebuild archive -project <Pascal>Plugin.xcodeproj -scheme <Pascal>Plugin \
  -destination "generic/platform=iOS" \
  -archivePath build/<Pascal>Plugin-device.xcarchive \
  SKIP_INSTALL=NO BUILD_LIBRARY_FOR_DISTRIBUTION=YES

# copy the flat framework out (NO xcframework wrapper):
rm -rf ../ios/<Pascal>Plugin.framework
cp -R build/<Pascal>Plugin-device.xcarchive/Products/Library/Frameworks/<Pascal>Plugin.framework ../ios/<Pascal>Plugin.framework

strip -x ../ios/<Pascal>Plugin.framework/<Pascal>Plugin   # optional, if size matters

(If you built with CocoaPods instead of header-only, archive the .xcworkspace scheme rather than the .xcodeproj — otherwise identical: still device-only, still copy the flat framework.)

On failure, print the failing step + the most relevant xcodebuild error line and STOP with BLOCKED: xcodebuild failed — <line>. The likely standalone failures (and where they come from) are pinned in ppmplugin-format §5b — most route back to React linking (Step 2): symbol-not-found at link → React not weak-linked (-undefined dynamic_lookup missing); <React/…> not found → HEADER_SEARCH_PATHS wrong; symbol mismatch → the RN pin doesn't match the host's RN. Surface these as React/RN-version coupling issues, not extension-code bugs.

Verify the framework. ppmplugin/staging/ios/<Pascal>Plugin.framework/ MUST contain the two runtime-required items:

  • <Pascal>Plugin — the Mach-O binary, named exactly <Pascal>Plugin (the player dlopens Frameworks/<Pascal>Plugin.framework/<Pascal>Plugin; a differently-named binary won't load). REQUIRED.
  • Info.plist — REQUIRED (a framework bundle without it is invalid and fails the wrap codesign step).

If either is missing, STOP with BLOCKED: framework incomplete — missing <X> and the §5b fix (GENERATE_INFOPLIST_FILE=YES / the binary name == framework name). The umbrella header + Modules/module.modulemap are build hygiene — note them if absent, but do NOT block on them (the dlopen loader doesn't use them — §5b).


Step 6 — Report + next step

Update the ## ppmplugin (third-party controls) block in .extension-state.md: iOS framework: built <ISO timestamp> (<Pascal>Plugin.framework, device slice). Print a fenced summary with the .framework path (and note it carries umbrella + module.modulemap + Info.plist), then offer next steps via AskUserQuestion (shared-instructions §9.1 — invoke the chosen skill via the Skill tool; execute, don't describe):

  • Run /assemble-ppmplugin (recommended — zip the manifest + binaries into the .ppmplugin)
  • Run /build-android-binary (if you also need the Android slice for a Both bundle)
  • Stay — I'll inspect the framework first

Return DONE with the .framework path, or BLOCKED with the failing xcodebuild line (most often React linking / an RN-version mismatch with the host — not an extension-code fix).

想直接用这个技能?

本站把开放许可(MIT / Apache 等)的技能按仓库打包整理到网盘,点一下转存到你自己的网盘,不用一个个从 GitHub 拉。许可未声明的技能只给原始仓库链接,不打包。

它属于哪个仓库

星标★ 878
本站分层T2
该仓技能数113
原文件路径plugins/power-apps-mobile-extension/skills/build-ios-binary/SKILL.md

同一个仓库里的其他技能

看这个仓库的全部 113 个技能