@@ -218,16 +218,63 @@ jobs:
218218 ./scripts/sign_and_notarize.sh "$APP_PATH" --notarize
219219 fi
220220
221- # Sign CLI and its bundled lsl.framework
221+ # Sign CLI and its bundled lsl.framework + libusb
222222 CLI_PATH=$(find packages -name "MCCOutletCLI" -type f | head -1)
223223 if [[ -n "$CLI_PATH" ]]; then
224224 CLI_DIR=$(dirname "$CLI_PATH")
225- # Sign framework first (dependency must be signed before dependent)
225+ mkdir -p "$CLI_DIR/Frameworks"
226+
227+ # -----------------------------------------------------------------
228+ # Bundle libusb next to the CLI, exactly like lsl.framework.
229+ #
230+ # The CLI links libusb by an absolute Homebrew path. Once the CLI
231+ # is Developer-ID signed with the hardened runtime it is subject to
232+ # library validation and may only load dylibs signed by Apple or
233+ # the same Team ID. Homebrew's libusb bottle is signed by a
234+ # different team, so dyld refuses it unconditionally and the signed
235+ # CLI is unrunnable on every machine. Fix: copy libusb in, normalize
236+ # its install name to @rpath (resolved via the CLI's existing
237+ # @executable_path/Frameworks rpath), repoint the CLI, and sign it
238+ # with our identity BEFORE the CLI (dependency before dependent).
239+ # -----------------------------------------------------------------
240+ LIBUSB_DEST="$CLI_DIR/Frameworks/libusb-1.0.0.dylib"
241+ if [[ ! -f "$LIBUSB_DEST" ]]; then
242+ # Prefer the copy macdeployqt already placed in the app bundle,
243+ # otherwise fall back to the reference the CLI currently links
244+ # (brew prefix differs: arm64 /opt/homebrew, x86_64 /usr/local).
245+ APP_LIBUSB=$(find packages -path "*.app/Contents/Frameworks/libusb-1.0.0.dylib" -type f | head -1)
246+ if [[ -n "$APP_LIBUSB" ]]; then
247+ cp "$APP_LIBUSB" "$LIBUSB_DEST"
248+ else
249+ cp "$(otool -L "$CLI_PATH" | awk '/libusb-1\.0\.0\.dylib/{print $1; exit}')" "$LIBUSB_DEST"
250+ fi
251+ chmod u+w "$LIBUSB_DEST"
252+ fi
253+ install_name_tool -id @rpath/libusb-1.0.0.dylib "$LIBUSB_DEST"
254+
255+ # Repoint the CLI from the absolute brew path to the bundled copy.
256+ CLI_LIBUSB_REF=$(otool -L "$CLI_PATH" | awk '/libusb-1\.0\.0\.dylib/{print $1; exit}')
257+ if [[ -n "$CLI_LIBUSB_REF" && "$CLI_LIBUSB_REF" != "@rpath/libusb-1.0.0.dylib" ]]; then
258+ install_name_tool -change "$CLI_LIBUSB_REF" @rpath/libusb-1.0.0.dylib "$CLI_PATH"
259+ fi
260+
261+ # Sign bundled dependencies first (must precede the dependent CLI).
226262 if [[ -d "$CLI_DIR/Frameworks/lsl.framework" ]]; then
227263 codesign --force --sign "$APPLE_CODE_SIGN_IDENTITY_APP" --options runtime \
228264 "$CLI_DIR/Frameworks/lsl.framework"
229265 fi
266+ codesign --force --sign "$APPLE_CODE_SIGN_IDENTITY_APP" --options runtime \
267+ "$LIBUSB_DEST"
268+
230269 ./scripts/sign_and_notarize.sh "$CLI_PATH" --notarize
270+
271+ # Guardrail: a signed CLI that still links an absolute brew dylib is
272+ # dead on arrival under library validation. Fail the build instead.
273+ if otool -L "$CLI_PATH" | grep -qE '/(opt/homebrew|usr/local)/'; then
274+ echo "ERROR: signed CLI links non-bundled dylibs:"
275+ otool -L "$CLI_PATH"
276+ exit 1
277+ fi
231278 fi
232279
233280 # -----------------------------------------------------------------------
0 commit comments