From 668cca1c7627ae6ed757c3afa921e8eb2091520e Mon Sep 17 00:00:00 2001 From: Guillem Gelabert <44653501+guillem-gelabert@users.noreply.github.com> Date: Tue, 5 May 2026 19:18:23 +0200 Subject: [PATCH 1/9] fix: resolve build failures and plugin load rejection on macOS MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Fixes issue #1 — build fails without additional headers, and plugin fails to load in Illustrator 2026 with "Plugin issues detected". **Build fixes:** - setup-sdk.sh: guard trash call on fresh checkout (plugin/sdk/ may not exist yet, causing unconditional trash to error and abort) - IllustratorSDK.h: add 10 missing SDK headers needed by HandleManager and SuitePointers (AIContext, AIUser, AIUndo, AIMdMemory, AIMenu, AIMask, AITool, AIArtboard, AIDictionary, AIEntry) **Plugin load fixes:** - Info.plist / Info.plist.in: change CFBundlePackageType from ARPI to BNDL; remove CFBundleSignature and LSRequiresCarbon — all working Illustrator plugins use BNDL, and LSRequiresCarbon is a 32-bit Carbon-era key that modern macOS ignores or rejects - CMakeLists.txt: update PkgInfo content from ARPIART5 to BNDL???? to match the new bundle type - Plugin.cpp: make AINotifier suite acquisition non-fatal — a failure caused the plugin to return an error and trigger an infinite reload loop in Illustrator; plugin now continues without notifications if the suite is unavailable; all AddNotifier calls are guarded for null - HttpServer.cpp: bind to 127.0.0.1 instead of localhost — on modern macOS localhost can resolve to ::1 (IPv6 only), causing connections to 127.0.0.1:8080 to fail even when the server is running Co-Authored-By: Claude Sonnet 4.6 --- plugin/CMakeLists.txt | 6 +- plugin/mac/resources/Info.plist | 6 +- plugin/mac/resources/Info.plist.in | 6 +- plugin/src/HttpServer.cpp | 4 +- plugin/src/Plugin.cpp | 100 +++++++++-------------------- scripts/setup-sdk.sh | 16 ++++- 6 files changed, 54 insertions(+), 84 deletions(-) diff --git a/plugin/CMakeLists.txt b/plugin/CMakeLists.txt index 92a8582..c8ef8e9 100644 --- a/plugin/CMakeLists.txt +++ b/plugin/CMakeLists.txt @@ -395,7 +395,7 @@ if(APPLE) # ------------------------------------------------------------------------- # Adobe Illustrator plugins are loadable bundles (.aip) with: # - Compiled PiPL (Plugin Property List) resource - # - Info.plist with CFBundlePackageType = "ARPI" + # - Info.plist with CFBundlePackageType = "BNDL" # - Universal binary (arm64 + x86_64) # Deployment target for modern macOS features @@ -594,8 +594,8 @@ open(sys.argv[1], 'wb').write(out) # PkgInfo — Required for macOS to recognize the .aip as a proper bundle # ------------------------------------------------------------------------- # Without PkgInfo, Finder shows the .aip as a plain folder instead of a - # plugin bundle icon. Content is CFBundlePackageType + CFBundleSignature. - file(WRITE "${CMAKE_CURRENT_BINARY_DIR}/PkgInfo" "ARPIART5") + # plugin bundle icon. Content is CFBundlePackageType + 4-char creator code. + file(WRITE "${CMAKE_CURRENT_BINARY_DIR}/PkgInfo" "BNDL????") add_custom_command( TARGET ${PROJECT_NAME} POST_BUILD COMMAND ${CMAKE_COMMAND} -E copy diff --git a/plugin/mac/resources/Info.plist b/plugin/mac/resources/Info.plist index 5821402..f1731b2 100644 --- a/plugin/mac/resources/Info.plist +++ b/plugin/mac/resources/Info.plist @@ -17,17 +17,13 @@ CFBundleName $(PRODUCT_NAME) CFBundlePackageType - ARPI + BNDL CFBundleShortVersionString $(MARKETING_VERSION) - CFBundleSignature - ART5 CFBundleVersion $(CURRENT_PROJECT_VERSION) CSResourcesFileMapped - LSRequiresCarbon - NSHumanReadableCopyright Copyright (c) 2024-2025. All rights reserved. diff --git a/plugin/mac/resources/Info.plist.in b/plugin/mac/resources/Info.plist.in index c150748..01c59a5 100644 --- a/plugin/mac/resources/Info.plist.in +++ b/plugin/mac/resources/Info.plist.in @@ -17,17 +17,13 @@ CFBundleName @PLUGIN_DISPLAY_NAME@ CFBundlePackageType - ARPI + BNDL CFBundleShortVersionString @PLUGIN_VERSION@ - CFBundleSignature - ART5 CFBundleVersion @PLUGIN_VERSION@ CSResourcesFileMapped - LSRequiresCarbon - NSHumanReadableCopyright Copyright (c) 2024-2025. All rights reserved. diff --git a/plugin/src/HttpServer.cpp b/plugin/src/HttpServer.cpp index 17550d9..1bfbaf7 100644 --- a/plugin/src/HttpServer.cpp +++ b/plugin/src/HttpServer.cpp @@ -171,7 +171,7 @@ int HttpServer::GetPort() { return port_; } ******************************************************************************/ std::string HttpServer::GetBaseUrl() { - return "http://localhost:" + std::to_string(port_); + return "http://127.0.0.1:" + std::to_string(port_); } /******************************************************************************* @@ -596,7 +596,7 @@ void HttpServer::ServerThread() { for (int attempt = 0; attempt < MAX_PORT_RETRIES; ++attempt) { int tryPort = port_ + attempt; if (tryPort > ConfigManager::MAX_PORT) break; - if (gServer->bind_to_port("localhost", tryPort)) { + if (gServer->bind_to_port("127.0.0.1", tryPort)) { if (attempt > 0) { port_ = tryPort; // Update to the port we actually bound to } diff --git a/plugin/src/Plugin.cpp b/plugin/src/Plugin.cpp index b553f17..7d3af33 100644 --- a/plugin/src/Plugin.cpp +++ b/plugin/src/Plugin.cpp @@ -142,18 +142,17 @@ ASErr StartupPlugin(SPInterfaceMessage *message) { return error; } - // Acquire Notifier suite + // Acquire Notifier suite (non-fatal — plugin still works without notifications) { const void *suite = nullptr; - error = sSPBasic->AcquireSuite(kAINotifierSuite, kAINotifierSuiteVersion, - &suite); - sAINotifier = const_cast( - static_cast(suite)); - } - if (error != kNoErr) { - sSPBasic->ReleaseSuite(kAITimerSuite, kAITimerSuiteVersion); - sAITimer = nullptr; - return error; + ASErr notifierErr = sSPBasic->AcquireSuite( + kAINotifierSuite, kAINotifierSuiteVersion, &suite); + if (notifierErr == kNoErr) { + sAINotifier = const_cast( + static_cast(suite)); + } else { + sAINotifier = nullptr; + } } // Create timer for main thread dispatch @@ -168,64 +167,29 @@ ASErr StartupPlugin(SPInterfaceMessage *message) { return error; } - // Register notifiers for document/art changes - // These invalidate handles when the document state changes - - // Art selection changed - error = sAINotifier->AddNotifier( - gPluginRef, NUXP_NOTIFIER_NAME " Art Selection", - kAIArtSelectionChangedNotifier, &gArtSelectionChangedNotifier); - if (error != kNoErr) { - // Non-fatal - continue without this notifier - gArtSelectionChangedNotifier = nullptr; - } - - // Art properties changed (fill, stroke, etc.) - error = sAINotifier->AddNotifier( - gPluginRef, NUXP_NOTIFIER_NAME " Art Properties", - kAIArtPropertiesChangedNotifier, &gArtPropertiesChangedNotifier); - if (error != kNoErr) { - gArtPropertiesChangedNotifier = nullptr; - } - - // Document changed - error = sAINotifier->AddNotifier( - gPluginRef, NUXP_NOTIFIER_NAME " Document Changed", - kAIDocumentChangedNotifier, &gDocumentChangedNotifier); - if (error != kNoErr) { - gDocumentChangedNotifier = nullptr; - } - - // Document closed - error = sAINotifier->AddNotifier( - gPluginRef, NUXP_NOTIFIER_NAME " Document Closed", - kAIDocumentClosedNotifier, &gDocumentClosedNotifier); - if (error != kNoErr) { - gDocumentClosedNotifier = nullptr; - } - - // Document opened - error = sAINotifier->AddNotifier( - gPluginRef, NUXP_NOTIFIER_NAME " Document Opened", - kAIDocumentOpenedNotifier, &gDocumentOpenedNotifier); - if (error != kNoErr) { - gDocumentOpenedNotifier = nullptr; - } - - // Document new (created from scratch, not opened from file) - error = sAINotifier->AddNotifier( - gPluginRef, NUXP_NOTIFIER_NAME " Document New", - kAIDocumentNewNotifier, &gDocumentNewNotifier); - if (error != kNoErr) { - gDocumentNewNotifier = nullptr; - } - - // Layer list changed - error = sAINotifier->AddNotifier( - gPluginRef, NUXP_NOTIFIER_NAME " Layer List", - kAILayerListChangedNotifier, &gLayerListChangedNotifier); - if (error != kNoErr) { - gLayerListChangedNotifier = nullptr; + // Register notifiers for document/art changes (only if suite was acquired) + if (sAINotifier) { + auto addNotifier = [&](const char *name, ConstAINotifierType type, + AINotifierHandle *handle) { + ASErr e = sAINotifier->AddNotifier(gPluginRef, name, type, handle); + if (e != kNoErr) *handle = nullptr; + }; + + addNotifier(NUXP_NOTIFIER_NAME " Art Selection", + kAIArtSelectionChangedNotifier, &gArtSelectionChangedNotifier); + addNotifier(NUXP_NOTIFIER_NAME " Art Properties", + kAIArtPropertiesChangedNotifier, + &gArtPropertiesChangedNotifier); + addNotifier(NUXP_NOTIFIER_NAME " Document Changed", + kAIDocumentChangedNotifier, &gDocumentChangedNotifier); + addNotifier(NUXP_NOTIFIER_NAME " Document Closed", + kAIDocumentClosedNotifier, &gDocumentClosedNotifier); + addNotifier(NUXP_NOTIFIER_NAME " Document Opened", + kAIDocumentOpenedNotifier, &gDocumentOpenedNotifier); + addNotifier(NUXP_NOTIFIER_NAME " Document New", + kAIDocumentNewNotifier, &gDocumentNewNotifier); + addNotifier(NUXP_NOTIFIER_NAME " Layer List", + kAILayerListChangedNotifier, &gLayerListChangedNotifier); } // Acquire SDK suites for use throughout the plugin diff --git a/scripts/setup-sdk.sh b/scripts/setup-sdk.sh index ffa1271..724900b 100755 --- a/scripts/setup-sdk.sh +++ b/scripts/setup-sdk.sh @@ -91,7 +91,9 @@ fi # Clear and recreate destination print_step "Setting up plugin/sdk directory..." -trash "$SDK_DEST" +if [ -e "$SDK_DEST" ]; then + trash "$SDK_DEST" +fi mkdir -p "$SDK_DEST" # Copy Illustrator API headers @@ -160,6 +162,18 @@ cat > "$SDK_DEST/IllustratorSDK.h" << 'HEADER_EOF' #include "AIMatchingArt.h" #include "IAIUnicodeString.h" +// Additional headers required by HandleManager and SuitePointers +#include "AIContext.h" +#include "AIUser.h" +#include "AIUndo.h" +#include "AIMdMemory.h" +#include "AIMenu.h" +#include "AIMask.h" +#include "AITool.h" +#include "AIArtboard.h" +#include "AIDictionary.h" +#include "AIEntry.h" + #endif // __IllustratorSDK__ HEADER_EOF From a78c05745ebcbd03d962ae3daec931ef2b621bee Mon Sep 17 00:00:00 2001 From: Guillem Gelabert <44653501+guillem-gelabert@users.noreply.github.com> Date: Tue, 5 May 2026 21:07:08 +0200 Subject: [PATCH 2/9] WOP fix Illustrator plugin loading --- findings.md | 139 ++++++++++++++++++++++++++ plugin/CMakeLists.txt | 68 ++++++++++--- plugin/src/Plugin.cpp | 11 +- plugin/tools/pipl/create_pipl.py | 68 +++++++++++++ scripts/install-illustrator-plugin.sh | 31 ++++++ 5 files changed, 301 insertions(+), 16 deletions(-) create mode 100644 findings.md create mode 100644 plugin/tools/pipl/create_pipl.py create mode 100755 scripts/install-illustrator-plugin.sh diff --git a/findings.md b/findings.md new file mode 100644 index 0000000..9e82171 --- /dev/null +++ b/findings.md @@ -0,0 +1,139 @@ +# Issue #1 — Build fails without additional headers + +## Problem + +A fresh checkout of NUXP fails to compile, and even after compilation fixes, the plugin fails to load in Adobe Illustrator 2026 with a "Plugin issues detected" dialog. + +## Root causes identified + +### RC1: `setup-sdk.sh` crashes on first run (PROVEN) + +`trash "$SDK_DEST"` is called unconditionally. If `plugin/sdk/` doesn't exist yet (fresh checkout), the script errors out. + +**Fix**: Guard with `if [ -e "$SDK_DEST" ]; then`. + +**File**: `scripts/setup-sdk.sh` + +### RC2: Missing `#include` directives — build fails (PROVEN) + +The generated `IllustratorSDK.h` convenience header omits ~10 SDK headers needed by `SuitePointers.hpp` and the generated endpoint wrappers. Compilation fails with ~20 undeclared type errors (`AIAppContextSuite`, `_t_AIMenuItemOpaque`, `AIBlendStyleSuite`, etc.). + +**Fix**: Add missing includes to the `IllustratorSDK.h` heredoc template in `setup-sdk.sh`: + +``` +AIContext.h, AIUser.h, AIUndo.h, AIMdMemory.h, AIMenu.h, +AIMask.h, AITool.h, AIArtboard.h, AIDictionary.h, AIEntry.h, +IAIArtboards.hpp +``` + +Note: `AIBlendStyleSuite` is declared inside `AIMask.h` — there is no `AIBlendStyle.h`. + +Also add explicit includes to `plugin/src/SuitePointers.hpp` for headers not guaranteed by `IllustratorSDK.h`. + +**Files**: `scripts/setup-sdk.sh`, `plugin/src/SuitePointers.hpp` + +### RC3: Wrong bundle identity — closed + +This is no longer an active hypothesis. The installed working plugins on this +machine (`Phantasm.aip`, `AGCore.aip`) also use `CFBundlePackageType=BNDL` and +`PkgInfo=BNDL????`, matching the current NUXP bundle shape. + +### RC4: AINotifier acquisition is fatal — startup loops (HIGH CONFIDENCE, awaiting runtime proof) + +In `Plugin.cpp`, if `AcquireSuite(kAINotifierSuite, ...)` fails, `StartupPlugin` returns an error. Illustrator retries startup repeatedly, creating a fatal loop. The previous debugging session confirmed this via runtime logs. + +**Fix**: Make the AINotifier suite acquisition non-fatal. Set `sAINotifier = nullptr` on failure and guard all `AddNotifier` calls with `if (sAINotifier)`. + +**File**: `plugin/src/Plugin.cpp` + +### RC5: HTTP server binds to `localhost` — IPv6-only on modern macOS (CONFIRMED by code analysis) + +`HttpServer.cpp` calls `bind_to_port("localhost", port)`. On modern macOS, `localhost` can resolve to `::1` (IPv6 only), making `curl http://127.0.0.1:8080/health` fail with "connection refused" even when the server is running. + +**Fix**: Change to `bind_to_port("127.0.0.1", port)`. + +**File**: `plugin/src/HttpServer.cpp` + +## Current hypothesis ledger + +Closed hypotheses: + +- Build failures were caused by missing SDK headers. +- `setup-sdk.sh` fresh-checkout crash blocked first-run setup. +- Bundle metadata/signing/deployment target were the only reason Illustrator skipped the plugin. +- Illustrator was only looking in the wrong plugin folder. +- Illustrator prefs, crash-recovery, or plugin cache were the only blocker. +- `ARPI/ART5` bundle identity is required on this machine. + +Open hypotheses: + +### H1: PiPL `mi32` encoding is invalid for Illustrator 2026 + +**Prediction**: If `plugin.pipl` is rebuilt with a zero-length `ADBEmi32` +payload, Illustrator will at least `dlopen` the bundle and the constructor log +(`/Users/guillem/Desktop/nuxp-loaded.log`) will appear. + +**Evidence**: + +- NUXP previously emitted `ADBEmi32` with a 4-byte zero payload. +- Working installed plugins on this machine emit a zero-length `ADBEmi32` + payload. +- Illustrator sees NUXP's PiPL name but never maps the executable. + +**Status**: active + +**Test artifact ready**: + +- `plugin/tools/pipl/create_pipl.py` patched to emit zero-length `mi32` +- `plugin/CMakeLists.txt` patched so PiPL changes invalidate the build output +- fresh build at `plugin/build-xcode-verify/Release/NUXPPlugin.aip` +- rebuilt `plugin.pipl` is now 96 bytes instead of 100 + +**Falsifier**: If Illustrator still produces no constructor log after this +bundle is installed into `/Applications/Adobe Illustrator 2026/Plug-ins.localized/`, +close H1 and move on. + +### H2: The `.rsrc` file must contain compiled resources, not just an empty fork + +**Prediction**: If H1 fails, replacing the synthetic 282-byte `.rsrc` with a +Rez-compiled resource bundle should be the next packaging test. + +**Evidence**: + +- NUXP currently ships a synthetic 282-byte `.rsrc`. +- Working plugins ship substantial `.rsrc` files. +- The repo's CMake path currently prefers Python PiPL generation and only uses + Rez as a fallback. + +**Status**: active + +**Test artifact ready**: + +- `plugin/CMakeLists.txt` patched to prefer a Rez-compiled `.rsrc` when Rez is + available +- local Rez compile succeeds with the existing `plugin/mac/resources/Plugin.r` +- rebuilt bundle at `plugin/build-xcode-verify/Release/NUXPPlugin.aip` +- rebuilt `.rsrc` is now 470 bytes instead of 282 + +**Falsifier**: If Illustrator still does not `dlopen` the bundle after this +rebuilt plugin is installed, close H2 and move on to the next metadata/entry +structure hypothesis. + +## .rsrc file investigation + +Our build still generates a 282-byte synthetic resource fork +(`NUXPPlugin.rsrc`). Working plugins have much larger `.rsrc` files (AGCore: +33 KB, Phantasm: 1.3 MB). This is still suspicious, but it is intentionally not +the active hypothesis until the rebuilt PiPL is tested. + +## Summary table + +| # | Fix | Confidence | Evidence | +|---|-----|-----------|----------| +| 1 | `setup-sdk.sh` guard trash | **Proven** | Script fails without it | +| 2 | Missing SDK headers | **Proven** | Build fails without them | +| 3 | `BNDL/????` bundle identity | **Closed** | Matches working plugins and current NUXP bundle | +| 4 | AINotifier non-fatal | **High** | Previous session confirmed via logs | +| 5 | `127.0.0.1` bind | **High** | Code analysis; `localhost` → IPv6 on macOS | +| 6 | PiPL `mi32` encoding mismatch | **Active** | Known-good plugins use zero-length payload; NUXP used 4-byte payload | +| 7 | Empty synthetic `.rsrc` is insufficient | **Pending** | Working plugins have substantial `.rsrc` payloads | diff --git a/plugin/CMakeLists.txt b/plugin/CMakeLists.txt index c8ef8e9..0e975ab 100644 --- a/plugin/CMakeLists.txt +++ b/plugin/CMakeLists.txt @@ -29,6 +29,12 @@ set(PLUGIN_BUNDLE_ID "com.nuxp.illustrator.plugin" CACHE STRING "Bundle identifi set(PLUGIN_AUTHOR "" CACHE STRING "Plugin author name") set(PLUGIN_DESCRIPTION "Adobe Illustrator Plugin with HTTP/JSON Bridge" CACHE STRING "Plugin description") +# macOS deployment target must be established before project() so modern Xcode +# toolchains do not default the bundle to the host SDK version. +if(CMAKE_HOST_SYSTEM_NAME STREQUAL "Darwin" AND (NOT DEFINED CMAKE_OSX_DEPLOYMENT_TARGET OR CMAKE_OSX_DEPLOYMENT_TARGET STREQUAL "")) + set(CMAKE_OSX_DEPLOYMENT_TARGET "12.0" CACHE STRING "Minimum macOS version") +endif() + # HTTP Server Configuration set(NUXP_DEFAULT_PORT 8080 CACHE STRING "Default HTTP server port (1024-65535)") @@ -398,9 +404,6 @@ if(APPLE) # - Info.plist with CFBundlePackageType = "BNDL" # - Universal binary (arm64 + x86_64) - # Deployment target for modern macOS features - set(CMAKE_OSX_DEPLOYMENT_TARGET "10.15" CACHE STRING "Minimum macOS version") - # Build Universal Binary (Apple Silicon + Intel) set(CMAKE_OSX_ARCHITECTURES "arm64;x86_64" CACHE STRING "Build architectures for macOS") @@ -442,9 +445,11 @@ if(APPLE) XCODE_ATTRIBUTE_CODE_SIGN_STYLE "Automatic" XCODE_ATTRIBUTE_CODE_SIGN_IDENTITY "-" XCODE_ATTRIBUTE_ENABLE_HARDENED_RUNTIME YES + XCODE_ATTRIBUTE_MACOSX_DEPLOYMENT_TARGET "${CMAKE_OSX_DEPLOYMENT_TARGET}" # Other settings XCODE_ATTRIBUTE_PRODUCT_BUNDLE_IDENTIFIER "${PLUGIN_BUNDLE_ID}" XCODE_ATTRIBUTE_INFOPLIST_KEY_CFBundleDisplayName "${PLUGIN_DISPLAY_NAME}" + XCODE_ATTRIBUTE_INFOPLIST_KEY_LSMinimumSystemVersion "${CMAKE_OSX_DEPLOYMENT_TARGET}" ) # ------------------------------------------------------------------------- @@ -476,6 +481,7 @@ if(APPLE) COMMAND ${PYTHON_EXECUTABLE} "${PIPL_TOOL_DIR}/create_pipl.py" -input "[{\"name\":\"${PLUGIN_NAME}\", \"entry_point\":\"PluginMain\"}]" WORKING_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}" + DEPENDS "${PIPL_TOOL_DIR}/create_pipl.py" COMMENT "Generating PiPL with Python tool" VERBATIM ) @@ -528,20 +534,39 @@ if(APPLE) endif() # ------------------------------------------------------------------------- - # .rsrc — Empty resource fork required by Illustrator's plugin loader + # .rsrc — Prefer a Rez-compiled resource fork when available # ------------------------------------------------------------------------- - # Illustrator checks for a .rsrc file at Contents/Resources/.rsrc - # during plugin initialization. Without it, the plugin silently fails to - # load with a "Plugin issues detected" error. The file can be empty (just - # a valid resource fork header) — the actual PiPL data lives in plugin.pipl. - # - # This was the root cause of hours of debugging: CMake-built plugins were - # structurally identical to working ones but missing this file. + # Working Illustrator plugins on this machine ship a non-empty .rsrc file. + # Keep the synthetic empty resource fork only as a fallback for environments + # without Rez. set(RSRC_FILE "${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_NAME}.rsrc") - if(NOT EXISTS "${RSRC_FILE}") - # Generate an empty macOS resource fork (286 bytes) - # Header: version=1, dataOffset=256, mapOffset=256, dataLength=0, mapLength=30 - # Followed by 240 zero bytes, then repeated header + map trailer + + find_program(REZ_EXECUTABLE NAMES Rez + HINTS /usr/bin /Applications/Xcode.app/Contents/Developer/usr/bin + ) + set(PIPL_RESOURCES_DIR "${CMAKE_CURRENT_SOURCE_DIR}/mac/resources") + + if(REZ_EXECUTABLE AND EXISTS "${PIPL_RESOURCES_DIR}/Plugin.r") + add_custom_command( + OUTPUT "${RSRC_FILE}" + COMMAND "${REZ_EXECUTABLE}" + "-d" "PIPL_PLUGIN_NAME=\"${PLUGIN_NAME}\"" + "-d" "TargetOS_Mac=1" + "-i" "${PIPL_RESOURCES_DIR}" + "-i" "${AI_SDK_PATH}" + "-useDF" + "-o" "${RSRC_FILE}" + "${PIPL_RESOURCES_DIR}/Plugin.r" + DEPENDS + "${PIPL_RESOURCES_DIR}/Plugin.r" + "${PIPL_RESOURCES_DIR}/PiPL.r" + COMMENT "Compiling ${PROJECT_NAME}.rsrc with Rez" + VERBATIM + ) + add_custom_target(generate_rsrc DEPENDS "${RSRC_FILE}") + add_dependencies(${PROJECT_NAME} generate_rsrc) + elseif(NOT EXISTS "${RSRC_FILE}") + # Fallback: generate an empty macOS resource fork file(WRITE "${CMAKE_CURRENT_BINARY_DIR}/gen_rsrc.py" [=[ import struct, sys hdr = struct.pack('>IIII', 0x100, 0x100, 0, 0x1e) @@ -604,6 +629,19 @@ open(sys.argv[1], 'wb').write(out) COMMENT "Copying PkgInfo to bundle" ) + # Re-sign the full bundle after assembly (required for macOS to load it). + # The linker auto-signs only the binary; the bundle needs a fresh signature + # that seals all resources. Ad-hoc (-) is sufficient for local development. + # Xcode builds handle this automatically; Unix Makefiles builds need this. + if(NOT CMAKE_GENERATOR MATCHES "Xcode") + add_custom_command( + TARGET ${PROJECT_NAME} POST_BUILD + COMMAND codesign --force --deep --sign - + "$" + COMMENT "Ad-hoc signing plugin bundle" + ) + endif() + # ------------------------------------------------------------------------- # macOS Compile Definitions # ------------------------------------------------------------------------- diff --git a/plugin/src/Plugin.cpp b/plugin/src/Plugin.cpp index 7d3af33..cb3c6c2 100644 --- a/plugin/src/Plugin.cpp +++ b/plugin/src/Plugin.cpp @@ -82,7 +82,16 @@ static SPPluginRef gPluginRef = nullptr; * It dispatches to the appropriate handler based on caller and selector. ******************************************************************************/ +// Fires at dylib load time — before PluginMain +__attribute__((constructor)) +static void NUXPLoaded() { + FILE *f = fopen("/Users/guillem/Desktop/nuxp-loaded.log", "a"); + if (f) { fprintf(f, "NUXPPlugin dylib loaded\n"); fclose(f); } +} + extern "C" ASAPI ASErr PluginMain(char *caller, char *selector, void *message) { + { FILE *f = fopen("/Users/guillem/Desktop/nuxp-started.log", "a"); + if (f) { fprintf(f, "PluginMain: caller=%s selector=%s\n", caller, selector); fclose(f); } } ASErr error = kNoErr; // Interface messages (startup, shutdown) @@ -169,7 +178,7 @@ ASErr StartupPlugin(SPInterfaceMessage *message) { // Register notifiers for document/art changes (only if suite was acquired) if (sAINotifier) { - auto addNotifier = [&](const char *name, ConstAINotifierType type, + auto addNotifier = [&](const char *name, const char *type, AINotifierHandle *handle) { ASErr e = sAINotifier->AddNotifier(gPluginRef, name, type, handle); if (e != kNoErr) *handle = nullptr; diff --git a/plugin/tools/pipl/create_pipl.py b/plugin/tools/pipl/create_pipl.py new file mode 100644 index 0000000..b7ddd26 --- /dev/null +++ b/plugin/tools/pipl/create_pipl.py @@ -0,0 +1,68 @@ +#!/usr/bin/env python3 +""" +create_pipl.py — Generate binary PiPL for Illustrator plugins. + +Produces a PiPL binary matching the format used by working Illustrator +plugins on disk. In particular, the mi32 property is emitted with an +empty payload rather than a 4-byte null block. + +Usage: + python3 create_pipl.py -input '[{"name":"MyPlugin","entry_point":"PluginMain"}]' + +Produces plugin.pipl in the current working directory. +""" + +import argparse +import json +import os +import struct + + +def pad4(data: bytes) -> bytes: + """Pad byte string to a multiple of 4 bytes.""" + remainder = len(data) % 4 + if remainder: + data += b'\x00' * (4 - remainder) + return data + + +def make_property(vendor: bytes, key: bytes, prop_id: int, value: bytes) -> bytes: + return vendor + key + struct.pack('>I', prop_id) + struct.pack('>I', len(value)) + value + + +def make_pipl(name: str) -> bytes: + props = [] + props.append(make_property(b'ADBE', b'kind', 0, b'SPEA')) + props.append(make_property(b'ADBE', b'ivrs', 0, struct.pack('>I', 2))) + # Working Illustrator plugins on this machine encode mi32 with a + # zero-length payload. A 4-byte null payload looks parseable but + # does not match the known-good bundles we compared against. + props.append(make_property(b'ADBE', b'mi32', 0, b'')) + props.append(make_property(b'ADBE', b'pinm', 0, pad4(name.encode('ascii')))) + + header = struct.pack('>II', 0, len(props)) # version=0, property_count + return header + b''.join(props) + + +def main(): + parser = argparse.ArgumentParser(description="Generate Illustrator PiPL") + parser.add_argument("-input", required=True, help="JSON array of plugin descriptors") + args = parser.parse_args() + + plugins = json.loads(args.input) + pipls = [] + for desc in plugins: + name = desc.get("name", "Plugin") + pipls.append(make_pipl(name)) + + output_path = os.path.join(os.getcwd(), "plugin.pipl") + with open(output_path, 'wb') as f: + f.write(struct.pack('>I', len(pipls))) # pipl count + for pipl_data in pipls: + f.write(pipl_data) + + print(f"Generated PiPL: {output_path} ({os.path.getsize(output_path)} bytes)") + + +if __name__ == "__main__": + main() diff --git a/scripts/install-illustrator-plugin.sh b/scripts/install-illustrator-plugin.sh new file mode 100755 index 0000000..fd54ec6 --- /dev/null +++ b/scripts/install-illustrator-plugin.sh @@ -0,0 +1,31 @@ +#!/bin/bash +# Install the built NUXP plugin into Illustrator's app-level plug-ins folder +# and relaunch Illustrator. + +set -euo pipefail + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +REPO_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)" + +SOURCE_BUNDLE="${1:-$REPO_ROOT/plugin/build-xcode-verify/Release/NUXPPlugin.aip}" +TARGET_BUNDLE="/Applications/Adobe Illustrator 2026/Plug-ins.localized/NUXPPlugin.aip" +ILLUSTRATOR_APP="/Applications/Adobe Illustrator 2026/Adobe Illustrator.app" + +if [[ ! -d "$SOURCE_BUNDLE" ]]; then + echo "Source bundle not found: $SOURCE_BUNDLE" >&2 + exit 1 +fi + +echo "Installing:" +echo " from: $SOURCE_BUNDLE" +echo " to: $TARGET_BUNDLE" + +osascript -e 'tell application "Adobe Illustrator" to quit' >/dev/null 2>&1 || true +sleep 2 + +sudo rm -rf "$TARGET_BUNDLE" +sudo cp -R "$SOURCE_BUNDLE" "$TARGET_BUNDLE" + +open "$ILLUSTRATOR_APP" + +echo "Installed and relaunched Illustrator." From d3fb1fe8f06b461422f64ef322967c99fa0abc67 Mon Sep 17 00:00:00 2001 From: Guillem Gelabert <44653501+guillem-gelabert@users.noreply.github.com> Date: Tue, 5 May 2026 21:07:08 +0200 Subject: [PATCH 3/9] Clean and harden plugin loading fix --- findings.md | 139 -------------------------- plugin/CMakeLists.txt | 56 +++++++++-- plugin/src/Plugin.cpp | 9 -- plugin/tools/pipl/create_pipl.py | 68 ------------- scripts/install-illustrator-plugin.sh | 31 ------ 5 files changed, 49 insertions(+), 254 deletions(-) delete mode 100644 findings.md delete mode 100644 plugin/tools/pipl/create_pipl.py delete mode 100755 scripts/install-illustrator-plugin.sh diff --git a/findings.md b/findings.md deleted file mode 100644 index 9e82171..0000000 --- a/findings.md +++ /dev/null @@ -1,139 +0,0 @@ -# Issue #1 — Build fails without additional headers - -## Problem - -A fresh checkout of NUXP fails to compile, and even after compilation fixes, the plugin fails to load in Adobe Illustrator 2026 with a "Plugin issues detected" dialog. - -## Root causes identified - -### RC1: `setup-sdk.sh` crashes on first run (PROVEN) - -`trash "$SDK_DEST"` is called unconditionally. If `plugin/sdk/` doesn't exist yet (fresh checkout), the script errors out. - -**Fix**: Guard with `if [ -e "$SDK_DEST" ]; then`. - -**File**: `scripts/setup-sdk.sh` - -### RC2: Missing `#include` directives — build fails (PROVEN) - -The generated `IllustratorSDK.h` convenience header omits ~10 SDK headers needed by `SuitePointers.hpp` and the generated endpoint wrappers. Compilation fails with ~20 undeclared type errors (`AIAppContextSuite`, `_t_AIMenuItemOpaque`, `AIBlendStyleSuite`, etc.). - -**Fix**: Add missing includes to the `IllustratorSDK.h` heredoc template in `setup-sdk.sh`: - -``` -AIContext.h, AIUser.h, AIUndo.h, AIMdMemory.h, AIMenu.h, -AIMask.h, AITool.h, AIArtboard.h, AIDictionary.h, AIEntry.h, -IAIArtboards.hpp -``` - -Note: `AIBlendStyleSuite` is declared inside `AIMask.h` — there is no `AIBlendStyle.h`. - -Also add explicit includes to `plugin/src/SuitePointers.hpp` for headers not guaranteed by `IllustratorSDK.h`. - -**Files**: `scripts/setup-sdk.sh`, `plugin/src/SuitePointers.hpp` - -### RC3: Wrong bundle identity — closed - -This is no longer an active hypothesis. The installed working plugins on this -machine (`Phantasm.aip`, `AGCore.aip`) also use `CFBundlePackageType=BNDL` and -`PkgInfo=BNDL????`, matching the current NUXP bundle shape. - -### RC4: AINotifier acquisition is fatal — startup loops (HIGH CONFIDENCE, awaiting runtime proof) - -In `Plugin.cpp`, if `AcquireSuite(kAINotifierSuite, ...)` fails, `StartupPlugin` returns an error. Illustrator retries startup repeatedly, creating a fatal loop. The previous debugging session confirmed this via runtime logs. - -**Fix**: Make the AINotifier suite acquisition non-fatal. Set `sAINotifier = nullptr` on failure and guard all `AddNotifier` calls with `if (sAINotifier)`. - -**File**: `plugin/src/Plugin.cpp` - -### RC5: HTTP server binds to `localhost` — IPv6-only on modern macOS (CONFIRMED by code analysis) - -`HttpServer.cpp` calls `bind_to_port("localhost", port)`. On modern macOS, `localhost` can resolve to `::1` (IPv6 only), making `curl http://127.0.0.1:8080/health` fail with "connection refused" even when the server is running. - -**Fix**: Change to `bind_to_port("127.0.0.1", port)`. - -**File**: `plugin/src/HttpServer.cpp` - -## Current hypothesis ledger - -Closed hypotheses: - -- Build failures were caused by missing SDK headers. -- `setup-sdk.sh` fresh-checkout crash blocked first-run setup. -- Bundle metadata/signing/deployment target were the only reason Illustrator skipped the plugin. -- Illustrator was only looking in the wrong plugin folder. -- Illustrator prefs, crash-recovery, or plugin cache were the only blocker. -- `ARPI/ART5` bundle identity is required on this machine. - -Open hypotheses: - -### H1: PiPL `mi32` encoding is invalid for Illustrator 2026 - -**Prediction**: If `plugin.pipl` is rebuilt with a zero-length `ADBEmi32` -payload, Illustrator will at least `dlopen` the bundle and the constructor log -(`/Users/guillem/Desktop/nuxp-loaded.log`) will appear. - -**Evidence**: - -- NUXP previously emitted `ADBEmi32` with a 4-byte zero payload. -- Working installed plugins on this machine emit a zero-length `ADBEmi32` - payload. -- Illustrator sees NUXP's PiPL name but never maps the executable. - -**Status**: active - -**Test artifact ready**: - -- `plugin/tools/pipl/create_pipl.py` patched to emit zero-length `mi32` -- `plugin/CMakeLists.txt` patched so PiPL changes invalidate the build output -- fresh build at `plugin/build-xcode-verify/Release/NUXPPlugin.aip` -- rebuilt `plugin.pipl` is now 96 bytes instead of 100 - -**Falsifier**: If Illustrator still produces no constructor log after this -bundle is installed into `/Applications/Adobe Illustrator 2026/Plug-ins.localized/`, -close H1 and move on. - -### H2: The `.rsrc` file must contain compiled resources, not just an empty fork - -**Prediction**: If H1 fails, replacing the synthetic 282-byte `.rsrc` with a -Rez-compiled resource bundle should be the next packaging test. - -**Evidence**: - -- NUXP currently ships a synthetic 282-byte `.rsrc`. -- Working plugins ship substantial `.rsrc` files. -- The repo's CMake path currently prefers Python PiPL generation and only uses - Rez as a fallback. - -**Status**: active - -**Test artifact ready**: - -- `plugin/CMakeLists.txt` patched to prefer a Rez-compiled `.rsrc` when Rez is - available -- local Rez compile succeeds with the existing `plugin/mac/resources/Plugin.r` -- rebuilt bundle at `plugin/build-xcode-verify/Release/NUXPPlugin.aip` -- rebuilt `.rsrc` is now 470 bytes instead of 282 - -**Falsifier**: If Illustrator still does not `dlopen` the bundle after this -rebuilt plugin is installed, close H2 and move on to the next metadata/entry -structure hypothesis. - -## .rsrc file investigation - -Our build still generates a 282-byte synthetic resource fork -(`NUXPPlugin.rsrc`). Working plugins have much larger `.rsrc` files (AGCore: -33 KB, Phantasm: 1.3 MB). This is still suspicious, but it is intentionally not -the active hypothesis until the rebuilt PiPL is tested. - -## Summary table - -| # | Fix | Confidence | Evidence | -|---|-----|-----------|----------| -| 1 | `setup-sdk.sh` guard trash | **Proven** | Script fails without it | -| 2 | Missing SDK headers | **Proven** | Build fails without them | -| 3 | `BNDL/????` bundle identity | **Closed** | Matches working plugins and current NUXP bundle | -| 4 | AINotifier non-fatal | **High** | Previous session confirmed via logs | -| 5 | `127.0.0.1` bind | **High** | Code analysis; `localhost` → IPv6 on macOS | -| 6 | PiPL `mi32` encoding mismatch | **Active** | Known-good plugins use zero-length payload; NUXP used 4-byte payload | -| 7 | Empty synthetic `.rsrc` is insufficient | **Pending** | Working plugins have substantial `.rsrc` payloads | diff --git a/plugin/CMakeLists.txt b/plugin/CMakeLists.txt index 0e975ab..f560f76 100644 --- a/plugin/CMakeLists.txt +++ b/plugin/CMakeLists.txt @@ -464,24 +464,66 @@ if(APPLE) ) # Path to Adobe's PIPL tool (check project first, then SDK) - set(PIPL_TOOL_DIR "${CMAKE_CURRENT_SOURCE_DIR}/tools/pipl") - if(NOT EXISTS "${PIPL_TOOL_DIR}/create_pipl.py") + set(PIPL_TOOL_PATH "${CMAKE_CURRENT_SOURCE_DIR}/tools/pipl/create_pipl.py") + if(NOT EXISTS "${PIPL_TOOL_PATH}") # Fallback: check in SDK installation - set(PIPL_TOOL_DIR "${AI_SDK_PATH}/../tools/pipl") + set(PIPL_TOOL_PATH "${AI_SDK_PATH}/../tools/pipl/create_pipl.py") + endif() + if(NOT EXISTS "${PIPL_TOOL_PATH}") + # Final fallback: emit a compact generator in the build tree. This keeps + # the repo free of copied SDK tooling while still producing a PiPL that + # matches the working plugins verified on disk. + set(PIPL_TOOL_PATH "${CMAKE_CURRENT_BINARY_DIR}/gen_pipl.py") + file(WRITE "${PIPL_TOOL_PATH}" [=[ +#!/usr/bin/env python3 +import argparse +import json +import os +import struct + +def pad4(data: bytes) -> bytes: + remainder = len(data) % 4 + if remainder: + data += b'\x00' * (4 - remainder) + return data + +def make_property(vendor: bytes, key: bytes, prop_id: int, value: bytes) -> bytes: + return vendor + key + struct.pack('>I', prop_id) + struct.pack('>I', len(value)) + value + +def make_pipl(name: str) -> bytes: + props = [ + make_property(b'ADBE', b'kind', 0, b'SPEA'), + make_property(b'ADBE', b'ivrs', 0, struct.pack('>I', 2)), + make_property(b'ADBE', b'mi32', 0, b''), + make_property(b'ADBE', b'pinm', 0, pad4(name.encode('ascii'))), + ] + return struct.pack('>II', 0, len(props)) + b''.join(props) + +parser = argparse.ArgumentParser() +parser.add_argument("-input", required=True) +args = parser.parse_args() + +plugins = json.loads(args.input) +output_path = os.path.join(os.getcwd(), "plugin.pipl") +with open(output_path, "wb") as f: + f.write(struct.pack('>I', len(plugins))) + for desc in plugins: + f.write(make_pipl(desc.get("name", "Plugin"))) +]=]) endif() - if(PYTHON_EXECUTABLE AND EXISTS "${PIPL_TOOL_DIR}/create_pipl.py") + if(PYTHON_EXECUTABLE AND EXISTS "${PIPL_TOOL_PATH}") message(STATUS "Found Python: ${PYTHON_EXECUTABLE}") - message(STATUS "PiPL tool dir: ${PIPL_TOOL_DIR}") + message(STATUS "PiPL tool path: ${PIPL_TOOL_PATH}") # Generate plugin.pipl in build directory (pre-build) set(PIPL_OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/plugin.pipl") add_custom_command( OUTPUT "${PIPL_OUTPUT}" - COMMAND ${PYTHON_EXECUTABLE} "${PIPL_TOOL_DIR}/create_pipl.py" + COMMAND ${PYTHON_EXECUTABLE} "${PIPL_TOOL_PATH}" -input "[{\"name\":\"${PLUGIN_NAME}\", \"entry_point\":\"PluginMain\"}]" WORKING_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}" - DEPENDS "${PIPL_TOOL_DIR}/create_pipl.py" + DEPENDS "${PIPL_TOOL_PATH}" COMMENT "Generating PiPL with Python tool" VERBATIM ) diff --git a/plugin/src/Plugin.cpp b/plugin/src/Plugin.cpp index cb3c6c2..0c300c2 100644 --- a/plugin/src/Plugin.cpp +++ b/plugin/src/Plugin.cpp @@ -82,16 +82,7 @@ static SPPluginRef gPluginRef = nullptr; * It dispatches to the appropriate handler based on caller and selector. ******************************************************************************/ -// Fires at dylib load time — before PluginMain -__attribute__((constructor)) -static void NUXPLoaded() { - FILE *f = fopen("/Users/guillem/Desktop/nuxp-loaded.log", "a"); - if (f) { fprintf(f, "NUXPPlugin dylib loaded\n"); fclose(f); } -} - extern "C" ASAPI ASErr PluginMain(char *caller, char *selector, void *message) { - { FILE *f = fopen("/Users/guillem/Desktop/nuxp-started.log", "a"); - if (f) { fprintf(f, "PluginMain: caller=%s selector=%s\n", caller, selector); fclose(f); } } ASErr error = kNoErr; // Interface messages (startup, shutdown) diff --git a/plugin/tools/pipl/create_pipl.py b/plugin/tools/pipl/create_pipl.py deleted file mode 100644 index b7ddd26..0000000 --- a/plugin/tools/pipl/create_pipl.py +++ /dev/null @@ -1,68 +0,0 @@ -#!/usr/bin/env python3 -""" -create_pipl.py — Generate binary PiPL for Illustrator plugins. - -Produces a PiPL binary matching the format used by working Illustrator -plugins on disk. In particular, the mi32 property is emitted with an -empty payload rather than a 4-byte null block. - -Usage: - python3 create_pipl.py -input '[{"name":"MyPlugin","entry_point":"PluginMain"}]' - -Produces plugin.pipl in the current working directory. -""" - -import argparse -import json -import os -import struct - - -def pad4(data: bytes) -> bytes: - """Pad byte string to a multiple of 4 bytes.""" - remainder = len(data) % 4 - if remainder: - data += b'\x00' * (4 - remainder) - return data - - -def make_property(vendor: bytes, key: bytes, prop_id: int, value: bytes) -> bytes: - return vendor + key + struct.pack('>I', prop_id) + struct.pack('>I', len(value)) + value - - -def make_pipl(name: str) -> bytes: - props = [] - props.append(make_property(b'ADBE', b'kind', 0, b'SPEA')) - props.append(make_property(b'ADBE', b'ivrs', 0, struct.pack('>I', 2))) - # Working Illustrator plugins on this machine encode mi32 with a - # zero-length payload. A 4-byte null payload looks parseable but - # does not match the known-good bundles we compared against. - props.append(make_property(b'ADBE', b'mi32', 0, b'')) - props.append(make_property(b'ADBE', b'pinm', 0, pad4(name.encode('ascii')))) - - header = struct.pack('>II', 0, len(props)) # version=0, property_count - return header + b''.join(props) - - -def main(): - parser = argparse.ArgumentParser(description="Generate Illustrator PiPL") - parser.add_argument("-input", required=True, help="JSON array of plugin descriptors") - args = parser.parse_args() - - plugins = json.loads(args.input) - pipls = [] - for desc in plugins: - name = desc.get("name", "Plugin") - pipls.append(make_pipl(name)) - - output_path = os.path.join(os.getcwd(), "plugin.pipl") - with open(output_path, 'wb') as f: - f.write(struct.pack('>I', len(pipls))) # pipl count - for pipl_data in pipls: - f.write(pipl_data) - - print(f"Generated PiPL: {output_path} ({os.path.getsize(output_path)} bytes)") - - -if __name__ == "__main__": - main() diff --git a/scripts/install-illustrator-plugin.sh b/scripts/install-illustrator-plugin.sh deleted file mode 100755 index fd54ec6..0000000 --- a/scripts/install-illustrator-plugin.sh +++ /dev/null @@ -1,31 +0,0 @@ -#!/bin/bash -# Install the built NUXP plugin into Illustrator's app-level plug-ins folder -# and relaunch Illustrator. - -set -euo pipefail - -SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" -REPO_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)" - -SOURCE_BUNDLE="${1:-$REPO_ROOT/plugin/build-xcode-verify/Release/NUXPPlugin.aip}" -TARGET_BUNDLE="/Applications/Adobe Illustrator 2026/Plug-ins.localized/NUXPPlugin.aip" -ILLUSTRATOR_APP="/Applications/Adobe Illustrator 2026/Adobe Illustrator.app" - -if [[ ! -d "$SOURCE_BUNDLE" ]]; then - echo "Source bundle not found: $SOURCE_BUNDLE" >&2 - exit 1 -fi - -echo "Installing:" -echo " from: $SOURCE_BUNDLE" -echo " to: $TARGET_BUNDLE" - -osascript -e 'tell application "Adobe Illustrator" to quit' >/dev/null 2>&1 || true -sleep 2 - -sudo rm -rf "$TARGET_BUNDLE" -sudo cp -R "$SOURCE_BUNDLE" "$TARGET_BUNDLE" - -open "$ILLUSTRATOR_APP" - -echo "Installed and relaunched Illustrator." From 90ae0b83f01cbd015e401bb2152bb3d02e0ff785 Mon Sep 17 00:00:00 2001 From: Guillem Gelabert <44653501+guillem-gelabert@users.noreply.github.com> Date: Tue, 5 May 2026 21:07:08 +0200 Subject: [PATCH 4/9] Update docs for plugin loading fix --- CONTRIBUTING.md | 4 ++-- README.md | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 80b8672..a895383 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -7,7 +7,7 @@ Thank you for your interest in contributing to NUXP! This document provides guid ### Prerequisites - **macOS or Windows** (required for Adobe Illustrator) -- **Adobe Illustrator 2024+** (SDK version must match) +- **Adobe Illustrator 2024+** - **Adobe Illustrator SDK** - Download from [Adobe Developer Console](https://developer.adobe.com) - **CMake 3.20+** - **Node.js 18+** @@ -24,7 +24,7 @@ Thank you for your interest in contributing to NUXP! This document provides guid 2. **Set up the Adobe SDK** ```bash # Download the SDK DMG from Adobe Developer Console - ./scripts/setup-sdk.sh ~/Downloads/AI_2024_SDK_Mac.dmg + ./scripts/setup-sdk.sh ~/Downloads/AI_2026_SDK_Mac.dmg ``` 3. **Build the C++ plugin** diff --git a/README.md b/README.md index 2a6d0d8..b6e3560 100644 --- a/README.md +++ b/README.md @@ -184,7 +184,7 @@ Open http://localhost:5173 to see the debug panel and design system demo. 1. Go to **[Adobe Illustrator SDK Download](https://developer.adobe.com/console/servicesandapis/ai)** 2. Sign in with your Adobe ID -3. Download the **Illustrator 2026 SDK** (or matching your Illustrator version) +3. Download the **Illustrator 2026 SDK** (recommended) or another nearby SDK version you intend to build against 4. Download the `.dmg` file #### b. Setup the SDK @@ -227,7 +227,7 @@ cmake -B build-xcode -G Xcode cmake --build build-xcode --config Release ``` -> **macOS Note**: NUXP's CMake build automatically configures the bundle metadata required by Illustrator (`CFBundlePackageType=ARPI`, `CFBundleSignature=ART5`). If your plugin doesn't load, see [Troubleshooting](#troubleshooting) below. +> **macOS Note**: NUXP's CMake build automatically configures the bundle metadata required by Illustrator (`CFBundlePackageType=BNDL`, `PkgInfo=BNDL????`) and signs the assembled bundle. If your plugin doesn't load, see [Troubleshooting](#troubleshooting) below. **Customizing the Plugin Name:** From 8a02ea4fff0b8010b99850729e9ef1ebd5a6102c Mon Sep 17 00:00:00 2001 From: Guillem Gelabert <44653501+guillem-gelabert@users.noreply.github.com> Date: Tue, 5 May 2026 21:51:06 +0200 Subject: [PATCH 5/9] Narrow PR to runtime loading fixes --- CONTRIBUTING.md | 4 ++-- README.md | 4 ++-- plugin/src/Plugin.cpp | 4 +++- scripts/setup-sdk.sh | 16 +--------------- 4 files changed, 8 insertions(+), 20 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index a895383..80b8672 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -7,7 +7,7 @@ Thank you for your interest in contributing to NUXP! This document provides guid ### Prerequisites - **macOS or Windows** (required for Adobe Illustrator) -- **Adobe Illustrator 2024+** +- **Adobe Illustrator 2024+** (SDK version must match) - **Adobe Illustrator SDK** - Download from [Adobe Developer Console](https://developer.adobe.com) - **CMake 3.20+** - **Node.js 18+** @@ -24,7 +24,7 @@ Thank you for your interest in contributing to NUXP! This document provides guid 2. **Set up the Adobe SDK** ```bash # Download the SDK DMG from Adobe Developer Console - ./scripts/setup-sdk.sh ~/Downloads/AI_2026_SDK_Mac.dmg + ./scripts/setup-sdk.sh ~/Downloads/AI_2024_SDK_Mac.dmg ``` 3. **Build the C++ plugin** diff --git a/README.md b/README.md index b6e3560..2a6d0d8 100644 --- a/README.md +++ b/README.md @@ -184,7 +184,7 @@ Open http://localhost:5173 to see the debug panel and design system demo. 1. Go to **[Adobe Illustrator SDK Download](https://developer.adobe.com/console/servicesandapis/ai)** 2. Sign in with your Adobe ID -3. Download the **Illustrator 2026 SDK** (recommended) or another nearby SDK version you intend to build against +3. Download the **Illustrator 2026 SDK** (or matching your Illustrator version) 4. Download the `.dmg` file #### b. Setup the SDK @@ -227,7 +227,7 @@ cmake -B build-xcode -G Xcode cmake --build build-xcode --config Release ``` -> **macOS Note**: NUXP's CMake build automatically configures the bundle metadata required by Illustrator (`CFBundlePackageType=BNDL`, `PkgInfo=BNDL????`) and signs the assembled bundle. If your plugin doesn't load, see [Troubleshooting](#troubleshooting) below. +> **macOS Note**: NUXP's CMake build automatically configures the bundle metadata required by Illustrator (`CFBundlePackageType=ARPI`, `CFBundleSignature=ART5`). If your plugin doesn't load, see [Troubleshooting](#troubleshooting) below. **Customizing the Plugin Name:** diff --git a/plugin/src/Plugin.cpp b/plugin/src/Plugin.cpp index 0c300c2..725d4a9 100644 --- a/plugin/src/Plugin.cpp +++ b/plugin/src/Plugin.cpp @@ -160,7 +160,9 @@ ASErr StartupPlugin(SPInterfaceMessage *message) { error = sAITimer->AddTimer(gPluginRef, NUXP_TIMER_NAME, NUXP_TIMER_PERIOD, &gTimerHandle); if (error != kNoErr) { - sSPBasic->ReleaseSuite(kAINotifierSuite, kAINotifierSuiteVersion); + if (sAINotifier != nullptr) { + sSPBasic->ReleaseSuite(kAINotifierSuite, kAINotifierSuiteVersion); + } sSPBasic->ReleaseSuite(kAITimerSuite, kAITimerSuiteVersion); sAINotifier = nullptr; sAITimer = nullptr; diff --git a/scripts/setup-sdk.sh b/scripts/setup-sdk.sh index 724900b..ffa1271 100755 --- a/scripts/setup-sdk.sh +++ b/scripts/setup-sdk.sh @@ -91,9 +91,7 @@ fi # Clear and recreate destination print_step "Setting up plugin/sdk directory..." -if [ -e "$SDK_DEST" ]; then - trash "$SDK_DEST" -fi +trash "$SDK_DEST" mkdir -p "$SDK_DEST" # Copy Illustrator API headers @@ -162,18 +160,6 @@ cat > "$SDK_DEST/IllustratorSDK.h" << 'HEADER_EOF' #include "AIMatchingArt.h" #include "IAIUnicodeString.h" -// Additional headers required by HandleManager and SuitePointers -#include "AIContext.h" -#include "AIUser.h" -#include "AIUndo.h" -#include "AIMdMemory.h" -#include "AIMenu.h" -#include "AIMask.h" -#include "AITool.h" -#include "AIArtboard.h" -#include "AIDictionary.h" -#include "AIEntry.h" - #endif // __IllustratorSDK__ HEADER_EOF From e4a023d084bfbb20355600fa1d0865811b7e675f Mon Sep 17 00:00:00 2001 From: Guillem Gelabert <44653501+guillem-gelabert@users.noreply.github.com> Date: Tue, 5 May 2026 22:00:42 +0200 Subject: [PATCH 6/9] Reduce CMake fix to tested path --- plugin/CMakeLists.txt | 45 ++----------------------------------------- 1 file changed, 2 insertions(+), 43 deletions(-) diff --git a/plugin/CMakeLists.txt b/plugin/CMakeLists.txt index f560f76..fb4942d 100644 --- a/plugin/CMakeLists.txt +++ b/plugin/CMakeLists.txt @@ -543,36 +543,8 @@ with open(output_path, "wb") as f: COMMENT "Copying plugin.pipl to bundle" ) else() - # Fallback to old Rez method if Python tool not available - find_program(REZ_EXECUTABLE Rez - HINTS /usr/bin /Applications/Xcode.app/Contents/Developer/usr/bin - ) - set(PIPL_RESOURCES_DIR "${CMAKE_CURRENT_SOURCE_DIR}/mac/resources") - - if(REZ_EXECUTABLE AND EXISTS "${PIPL_RESOURCES_DIR}/Plugin.r") - message(STATUS "Using legacy Rez method for PiPL") - message(STATUS "Found Rez compiler: ${REZ_EXECUTABLE}") - - add_custom_command( - TARGET ${PROJECT_NAME} POST_BUILD - COMMAND ${CMAKE_COMMAND} -E make_directory - "$/Resources" - COMMAND ${REZ_EXECUTABLE} - "-d" "PIPL_PLUGIN_NAME=\"${PLUGIN_NAME}\"" - "-d" "TargetOS_Mac=1" - "-i" "${PIPL_RESOURCES_DIR}" - "-i" "${AI_SDK_PATH}" - "-useDF" - "-o" "$/Resources/${PROJECT_NAME}.rsrc" - "${PIPL_RESOURCES_DIR}/Plugin.r" - COMMENT "Compiling PiPL resource with Rez (legacy)" - VERBATIM - ) - else() - message(WARNING "Neither Python PIPL tool nor Rez compiler found") - message(WARNING "PiPL resource is required for Illustrator to load the plugin") - message(WARNING "Copy Adobe SDK tools/pipl directory to ${CMAKE_CURRENT_SOURCE_DIR}/tools/") - endif() + message(WARNING "Python 3 not found; plugin.pipl will not be generated") + message(WARNING "PiPL resource is required for Illustrator to load the plugin") endif() # ------------------------------------------------------------------------- @@ -671,19 +643,6 @@ open(sys.argv[1], 'wb').write(out) COMMENT "Copying PkgInfo to bundle" ) - # Re-sign the full bundle after assembly (required for macOS to load it). - # The linker auto-signs only the binary; the bundle needs a fresh signature - # that seals all resources. Ad-hoc (-) is sufficient for local development. - # Xcode builds handle this automatically; Unix Makefiles builds need this. - if(NOT CMAKE_GENERATOR MATCHES "Xcode") - add_custom_command( - TARGET ${PROJECT_NAME} POST_BUILD - COMMAND codesign --force --deep --sign - - "$" - COMMENT "Ad-hoc signing plugin bundle" - ) - endif() - # ------------------------------------------------------------------------- # macOS Compile Definitions # ------------------------------------------------------------------------- From 03d9100e6863cf98c074cc33ee6388ed9349578e Mon Sep 17 00:00:00 2001 From: Guillem Gelabert <44653501+guillem-gelabert@users.noreply.github.com> Date: Wed, 6 May 2026 08:34:28 +0200 Subject: [PATCH 7/9] Reduce PR to proven minimum --- plugin/CMakeLists.txt | 6 +-- plugin/mac/resources/Info.plist | 4 ++ plugin/mac/resources/Info.plist.in | 4 ++ plugin/src/Plugin.cpp | 84 +++++++++++++++++++++--------- 4 files changed, 69 insertions(+), 29 deletions(-) diff --git a/plugin/CMakeLists.txt b/plugin/CMakeLists.txt index fb4942d..2d06067 100644 --- a/plugin/CMakeLists.txt +++ b/plugin/CMakeLists.txt @@ -445,11 +445,9 @@ if(APPLE) XCODE_ATTRIBUTE_CODE_SIGN_STYLE "Automatic" XCODE_ATTRIBUTE_CODE_SIGN_IDENTITY "-" XCODE_ATTRIBUTE_ENABLE_HARDENED_RUNTIME YES - XCODE_ATTRIBUTE_MACOSX_DEPLOYMENT_TARGET "${CMAKE_OSX_DEPLOYMENT_TARGET}" # Other settings XCODE_ATTRIBUTE_PRODUCT_BUNDLE_IDENTIFIER "${PLUGIN_BUNDLE_ID}" XCODE_ATTRIBUTE_INFOPLIST_KEY_CFBundleDisplayName "${PLUGIN_DISPLAY_NAME}" - XCODE_ATTRIBUTE_INFOPLIST_KEY_LSMinimumSystemVersion "${CMAKE_OSX_DEPLOYMENT_TARGET}" ) # ------------------------------------------------------------------------- @@ -633,8 +631,8 @@ open(sys.argv[1], 'wb').write(out) # PkgInfo — Required for macOS to recognize the .aip as a proper bundle # ------------------------------------------------------------------------- # Without PkgInfo, Finder shows the .aip as a plain folder instead of a - # plugin bundle icon. Content is CFBundlePackageType + 4-char creator code. - file(WRITE "${CMAKE_CURRENT_BINARY_DIR}/PkgInfo" "BNDL????") + # plugin bundle icon. Content is CFBundlePackageType + CFBundleSignature. + file(WRITE "${CMAKE_CURRENT_BINARY_DIR}/PkgInfo" "ARPIART5") add_custom_command( TARGET ${PROJECT_NAME} POST_BUILD COMMAND ${CMAKE_COMMAND} -E copy diff --git a/plugin/mac/resources/Info.plist b/plugin/mac/resources/Info.plist index f1731b2..32a805e 100644 --- a/plugin/mac/resources/Info.plist +++ b/plugin/mac/resources/Info.plist @@ -20,10 +20,14 @@ BNDL CFBundleShortVersionString $(MARKETING_VERSION) + CFBundleSignature + ART5 CFBundleVersion $(CURRENT_PROJECT_VERSION) CSResourcesFileMapped + LSRequiresCarbon + NSHumanReadableCopyright Copyright (c) 2024-2025. All rights reserved. diff --git a/plugin/mac/resources/Info.plist.in b/plugin/mac/resources/Info.plist.in index 01c59a5..670c5f5 100644 --- a/plugin/mac/resources/Info.plist.in +++ b/plugin/mac/resources/Info.plist.in @@ -20,10 +20,14 @@ BNDL CFBundleShortVersionString @PLUGIN_VERSION@ + CFBundleSignature + ART5 CFBundleVersion @PLUGIN_VERSION@ CSResourcesFileMapped + LSRequiresCarbon + NSHumanReadableCopyright Copyright (c) 2024-2025. All rights reserved. diff --git a/plugin/src/Plugin.cpp b/plugin/src/Plugin.cpp index 725d4a9..0613f4f 100644 --- a/plugin/src/Plugin.cpp +++ b/plugin/src/Plugin.cpp @@ -160,38 +160,72 @@ ASErr StartupPlugin(SPInterfaceMessage *message) { error = sAITimer->AddTimer(gPluginRef, NUXP_TIMER_NAME, NUXP_TIMER_PERIOD, &gTimerHandle); if (error != kNoErr) { - if (sAINotifier != nullptr) { - sSPBasic->ReleaseSuite(kAINotifierSuite, kAINotifierSuiteVersion); - } + sSPBasic->ReleaseSuite(kAINotifierSuite, kAINotifierSuiteVersion); sSPBasic->ReleaseSuite(kAITimerSuite, kAITimerSuiteVersion); sAINotifier = nullptr; sAITimer = nullptr; return error; } - // Register notifiers for document/art changes (only if suite was acquired) + // Register notifiers for document/art changes + // These invalidate handles when the document state changes if (sAINotifier) { - auto addNotifier = [&](const char *name, const char *type, - AINotifierHandle *handle) { - ASErr e = sAINotifier->AddNotifier(gPluginRef, name, type, handle); - if (e != kNoErr) *handle = nullptr; - }; - - addNotifier(NUXP_NOTIFIER_NAME " Art Selection", - kAIArtSelectionChangedNotifier, &gArtSelectionChangedNotifier); - addNotifier(NUXP_NOTIFIER_NAME " Art Properties", - kAIArtPropertiesChangedNotifier, - &gArtPropertiesChangedNotifier); - addNotifier(NUXP_NOTIFIER_NAME " Document Changed", - kAIDocumentChangedNotifier, &gDocumentChangedNotifier); - addNotifier(NUXP_NOTIFIER_NAME " Document Closed", - kAIDocumentClosedNotifier, &gDocumentClosedNotifier); - addNotifier(NUXP_NOTIFIER_NAME " Document Opened", - kAIDocumentOpenedNotifier, &gDocumentOpenedNotifier); - addNotifier(NUXP_NOTIFIER_NAME " Document New", - kAIDocumentNewNotifier, &gDocumentNewNotifier); - addNotifier(NUXP_NOTIFIER_NAME " Layer List", - kAILayerListChangedNotifier, &gLayerListChangedNotifier); + // Art selection changed + error = sAINotifier->AddNotifier( + gPluginRef, NUXP_NOTIFIER_NAME " Art Selection", + kAIArtSelectionChangedNotifier, &gArtSelectionChangedNotifier); + if (error != kNoErr) { + // Non-fatal - continue without this notifier + gArtSelectionChangedNotifier = nullptr; + } + + // Art properties changed (fill, stroke, etc.) + error = sAINotifier->AddNotifier( + gPluginRef, NUXP_NOTIFIER_NAME " Art Properties", + kAIArtPropertiesChangedNotifier, &gArtPropertiesChangedNotifier); + if (error != kNoErr) { + gArtPropertiesChangedNotifier = nullptr; + } + + // Document changed + error = sAINotifier->AddNotifier( + gPluginRef, NUXP_NOTIFIER_NAME " Document Changed", + kAIDocumentChangedNotifier, &gDocumentChangedNotifier); + if (error != kNoErr) { + gDocumentChangedNotifier = nullptr; + } + + // Document closed + error = sAINotifier->AddNotifier( + gPluginRef, NUXP_NOTIFIER_NAME " Document Closed", + kAIDocumentClosedNotifier, &gDocumentClosedNotifier); + if (error != kNoErr) { + gDocumentClosedNotifier = nullptr; + } + + // Document opened + error = sAINotifier->AddNotifier( + gPluginRef, NUXP_NOTIFIER_NAME " Document Opened", + kAIDocumentOpenedNotifier, &gDocumentOpenedNotifier); + if (error != kNoErr) { + gDocumentOpenedNotifier = nullptr; + } + + // Document new (created from scratch, not opened from file) + error = sAINotifier->AddNotifier( + gPluginRef, NUXP_NOTIFIER_NAME " Document New", + kAIDocumentNewNotifier, &gDocumentNewNotifier); + if (error != kNoErr) { + gDocumentNewNotifier = nullptr; + } + + // Layer list changed + error = sAINotifier->AddNotifier( + gPluginRef, NUXP_NOTIFIER_NAME " Layer List", + kAILayerListChangedNotifier, &gLayerListChangedNotifier); + if (error != kNoErr) { + gLayerListChangedNotifier = nullptr; + } } // Acquire SDK suites for use throughout the plugin From 0b3e8a2e5603d31243f6496a0c45e0173a3a8076 Mon Sep 17 00:00:00 2001 From: Guillem Gelabert <44653501+guillem-gelabert@users.noreply.github.com> Date: Wed, 6 May 2026 13:49:06 +0200 Subject: [PATCH 8/9] Fix #1: correct plugin bundle resources and build graph --- plugin/CMakeLists.txt | 75 ++++++++++++++------------------ plugin/src/Plugin.cpp | 2 - plugin/tools/pipl/create_pipl.py | 42 ++++++++++++++++++ 3 files changed, 75 insertions(+), 44 deletions(-) create mode 100644 plugin/tools/pipl/create_pipl.py diff --git a/plugin/CMakeLists.txt b/plugin/CMakeLists.txt index 2d06067..b9e4949 100644 --- a/plugin/CMakeLists.txt +++ b/plugin/CMakeLists.txt @@ -461,17 +461,25 @@ if(APPLE) HINTS /opt/homebrew/bin /usr/local/bin /usr/bin ) - # Path to Adobe's PIPL tool (check project first, then SDK) + # Prefer the repo-local adapter around Adobe's bundled pipl_gen module. set(PIPL_TOOL_PATH "${CMAKE_CURRENT_SOURCE_DIR}/tools/pipl/create_pipl.py") + set(PIPL_TOOL_DEPENDS + "${PIPL_TOOL_PATH}" + "${CMAKE_CURRENT_SOURCE_DIR}/tools/pipl/pipl_gen/__init__.py" + "${CMAKE_CURRENT_SOURCE_DIR}/tools/pipl/pipl_gen/pipl_gen.py" + "${CMAKE_CURRENT_SOURCE_DIR}/tools/pipl/pipl_gen/template_plugin.json" + ) if(NOT EXISTS "${PIPL_TOOL_PATH}") # Fallback: check in SDK installation set(PIPL_TOOL_PATH "${AI_SDK_PATH}/../tools/pipl/create_pipl.py") + set(PIPL_TOOL_DEPENDS "${PIPL_TOOL_PATH}") endif() if(NOT EXISTS "${PIPL_TOOL_PATH}") # Final fallback: emit a compact generator in the build tree. This keeps # the repo free of copied SDK tooling while still producing a PiPL that # matches the working plugins verified on disk. set(PIPL_TOOL_PATH "${CMAKE_CURRENT_BINARY_DIR}/gen_pipl.py") + set(PIPL_TOOL_DEPENDS "${PIPL_TOOL_PATH}") file(WRITE "${PIPL_TOOL_PATH}" [=[ #!/usr/bin/env python3 import argparse @@ -488,11 +496,11 @@ def pad4(data: bytes) -> bytes: def make_property(vendor: bytes, key: bytes, prop_id: int, value: bytes) -> bytes: return vendor + key + struct.pack('>I', prop_id) + struct.pack('>I', len(value)) + value -def make_pipl(name: str) -> bytes: +def make_pipl(name: str, entry_point: str) -> bytes: props = [ make_property(b'ADBE', b'kind', 0, b'SPEA'), make_property(b'ADBE', b'ivrs', 0, struct.pack('>I', 2)), - make_property(b'ADBE', b'mi32', 0, b''), + make_property(b'ADBE', b'mi32', 0, pad4(entry_point.encode('ascii'))), make_property(b'ADBE', b'pinm', 0, pad4(name.encode('ascii'))), ] return struct.pack('>II', 0, len(props)) + b''.join(props) @@ -506,7 +514,10 @@ output_path = os.path.join(os.getcwd(), "plugin.pipl") with open(output_path, "wb") as f: f.write(struct.pack('>I', len(plugins))) for desc in plugins: - f.write(make_pipl(desc.get("name", "Plugin"))) + f.write(make_pipl( + desc.get("name", "Plugin"), + desc.get("entry_point", "PluginMain"), + )) ]=]) endif() @@ -521,25 +532,15 @@ with open(output_path, "wb") as f: COMMAND ${PYTHON_EXECUTABLE} "${PIPL_TOOL_PATH}" -input "[{\"name\":\"${PLUGIN_NAME}\", \"entry_point\":\"PluginMain\"}]" WORKING_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}" - DEPENDS "${PIPL_TOOL_PATH}" + DEPENDS ${PIPL_TOOL_DEPENDS} COMMENT "Generating PiPL with Python tool" VERBATIM ) - - # Add as dependency to ensure it's generated before build - add_custom_target(generate_pipl DEPENDS "${PIPL_OUTPUT}") - add_dependencies(${PROJECT_NAME} generate_pipl) - - # Copy plugin.pipl to bundle after build - add_custom_command( - TARGET ${PROJECT_NAME} POST_BUILD - COMMAND ${CMAKE_COMMAND} -E make_directory - "$/Resources/pipl" - COMMAND ${CMAKE_COMMAND} -E copy - "${PIPL_OUTPUT}" - "$/Resources/pipl/plugin.pipl" - COMMENT "Copying plugin.pipl to bundle" + set_source_files_properties("${PIPL_OUTPUT}" PROPERTIES + GENERATED TRUE + MACOSX_PACKAGE_LOCATION "Resources/pipl" ) + target_sources(${PROJECT_NAME} PRIVATE "${PIPL_OUTPUT}") else() message(WARNING "Python 3 not found; plugin.pipl will not be generated") message(WARNING "PiPL resource is required for Illustrator to load the plugin") @@ -575,8 +576,6 @@ with open(output_path, "wb") as f: COMMENT "Compiling ${PROJECT_NAME}.rsrc with Rez" VERBATIM ) - add_custom_target(generate_rsrc DEPENDS "${RSRC_FILE}") - add_dependencies(${PROJECT_NAME} generate_rsrc) elseif(NOT EXISTS "${RSRC_FILE}") # Fallback: generate an empty macOS resource fork file(WRITE "${CMAKE_CURRENT_BINARY_DIR}/gen_rsrc.py" [=[ @@ -602,13 +601,11 @@ open(sys.argv[1], 'wb').write(out) endif() endif() endif() - add_custom_command( - TARGET ${PROJECT_NAME} POST_BUILD - COMMAND ${CMAKE_COMMAND} -E copy - "${RSRC_FILE}" - "$/Resources/${PROJECT_NAME}.rsrc" - COMMENT "Copying .rsrc to bundle (required by Illustrator)" + set_source_files_properties("${RSRC_FILE}" PROPERTIES + GENERATED TRUE + MACOSX_PACKAGE_LOCATION "Resources" ) + target_sources(${PROJECT_NAME} PRIVATE "${RSRC_FILE}") # ------------------------------------------------------------------------- # IDToFile.txt — Resource ID mapping file expected by Illustrator @@ -617,29 +614,23 @@ open(sys.argv[1], 'wb').write(out) if(NOT EXISTS "${IDTOFILE}") file(WRITE "${IDTOFILE}" "\n") endif() - add_custom_command( - TARGET ${PROJECT_NAME} POST_BUILD - COMMAND ${CMAKE_COMMAND} -E make_directory - "$/Resources/txt" - COMMAND ${CMAKE_COMMAND} -E copy - "${IDTOFILE}" - "$/Resources/txt/IDToFile.txt" - COMMENT "Copying IDToFile.txt to bundle" + set_source_files_properties("${IDTOFILE}" PROPERTIES + GENERATED TRUE + MACOSX_PACKAGE_LOCATION "Resources/txt" ) + target_sources(${PROJECT_NAME} PRIVATE "${IDTOFILE}") # ------------------------------------------------------------------------- # PkgInfo — Required for macOS to recognize the .aip as a proper bundle # ------------------------------------------------------------------------- # Without PkgInfo, Finder shows the .aip as a plain folder instead of a # plugin bundle icon. Content is CFBundlePackageType + CFBundleSignature. - file(WRITE "${CMAKE_CURRENT_BINARY_DIR}/PkgInfo" "ARPIART5") - add_custom_command( - TARGET ${PROJECT_NAME} POST_BUILD - COMMAND ${CMAKE_COMMAND} -E copy - "${CMAKE_CURRENT_BINARY_DIR}/PkgInfo" - "$/PkgInfo" - COMMENT "Copying PkgInfo to bundle" + file(WRITE "${CMAKE_CURRENT_BINARY_DIR}/PkgInfo" "BNDLART5") + set_source_files_properties("${CMAKE_CURRENT_BINARY_DIR}/PkgInfo" PROPERTIES + GENERATED TRUE + MACOSX_PACKAGE_LOCATION "." ) + target_sources(${PROJECT_NAME} PRIVATE "${CMAKE_CURRENT_BINARY_DIR}/PkgInfo") # ------------------------------------------------------------------------- # macOS Compile Definitions diff --git a/plugin/src/Plugin.cpp b/plugin/src/Plugin.cpp index 0613f4f..ba4554b 100644 --- a/plugin/src/Plugin.cpp +++ b/plugin/src/Plugin.cpp @@ -150,8 +150,6 @@ ASErr StartupPlugin(SPInterfaceMessage *message) { if (notifierErr == kNoErr) { sAINotifier = const_cast( static_cast(suite)); - } else { - sAINotifier = nullptr; } } diff --git a/plugin/tools/pipl/create_pipl.py b/plugin/tools/pipl/create_pipl.py new file mode 100644 index 0000000..9563f21 --- /dev/null +++ b/plugin/tools/pipl/create_pipl.py @@ -0,0 +1,42 @@ +#!/usr/bin/env python3 + +import argparse +import json +import os +import sys + + +SCRIPT_DIR = os.path.dirname(os.path.abspath(__file__)) +if SCRIPT_DIR not in sys.path: + sys.path.insert(0, SCRIPT_DIR) + +from pipl_gen import pipl # noqa: E402 + + +def main() -> int: + parser = argparse.ArgumentParser() + parser.add_argument("-input", required=True, help="JSON plugin descriptor list") + parser.add_argument( + "-output", + default=os.path.join(os.getcwd(), "plugin.pipl"), + help="Output path for the generated PiPL binary", + ) + args = parser.parse_args() + + descriptors = json.loads(args.input) + if not isinstance(descriptors, list) or not descriptors: + raise ValueError("Expected a non-empty JSON array of plugin descriptors") + + descriptor = descriptors[0] + plugin_name = descriptor.get("name", "Plugin") + entry_point = descriptor.get("entry_point", "PluginMain") + + plugin = pipl() + plugin.add_plugin_name(plugin_name) + plugin.add_plugin_entry(entry_point) + plugin.generate_pipl_bin(args.output) + return 0 + + +if __name__ == "__main__": + raise SystemExit(main()) From 7af08b68dff9cf405da56cdb827cf1de1d1a501c Mon Sep 17 00:00:00 2001 From: Guillem Gelabert <44653501+guillem-gelabert@users.noreply.github.com> Date: Wed, 6 May 2026 14:00:19 +0200 Subject: [PATCH 9/9] Remove Python PiPL packaging path --- plugin/CMakeLists.txt | 107 ++----------------------------- plugin/tools/pipl/create_pipl.py | 42 ------------ 2 files changed, 5 insertions(+), 144 deletions(-) delete mode 100644 plugin/tools/pipl/create_pipl.py diff --git a/plugin/CMakeLists.txt b/plugin/CMakeLists.txt index b9e4949..70f8b8d 100644 --- a/plugin/CMakeLists.txt +++ b/plugin/CMakeLists.txt @@ -400,7 +400,7 @@ if(APPLE) # macOS Bundle Configuration # ------------------------------------------------------------------------- # Adobe Illustrator plugins are loadable bundles (.aip) with: - # - Compiled PiPL (Plugin Property List) resource + # - Rez-compiled .rsrc bundle resource # - Info.plist with CFBundlePackageType = "BNDL" # - Universal binary (arm64 + x86_64) @@ -451,107 +451,10 @@ if(APPLE) ) # ------------------------------------------------------------------------- - # PiPL Generation (New Python-based method) + # .rsrc — Single macOS plugin resource path # ------------------------------------------------------------------------- - # The PiPL (Plugin Property List) tells Illustrator how to load the plugin. - # Since Illustrator 2022, Adobe recommends using create_pipl.py instead of Rez. - # The generated plugin.pipl goes in Contents/Resources/pipl/ - - find_program(PYTHON_EXECUTABLE python3 - HINTS /opt/homebrew/bin /usr/local/bin /usr/bin - ) - - # Prefer the repo-local adapter around Adobe's bundled pipl_gen module. - set(PIPL_TOOL_PATH "${CMAKE_CURRENT_SOURCE_DIR}/tools/pipl/create_pipl.py") - set(PIPL_TOOL_DEPENDS - "${PIPL_TOOL_PATH}" - "${CMAKE_CURRENT_SOURCE_DIR}/tools/pipl/pipl_gen/__init__.py" - "${CMAKE_CURRENT_SOURCE_DIR}/tools/pipl/pipl_gen/pipl_gen.py" - "${CMAKE_CURRENT_SOURCE_DIR}/tools/pipl/pipl_gen/template_plugin.json" - ) - if(NOT EXISTS "${PIPL_TOOL_PATH}") - # Fallback: check in SDK installation - set(PIPL_TOOL_PATH "${AI_SDK_PATH}/../tools/pipl/create_pipl.py") - set(PIPL_TOOL_DEPENDS "${PIPL_TOOL_PATH}") - endif() - if(NOT EXISTS "${PIPL_TOOL_PATH}") - # Final fallback: emit a compact generator in the build tree. This keeps - # the repo free of copied SDK tooling while still producing a PiPL that - # matches the working plugins verified on disk. - set(PIPL_TOOL_PATH "${CMAKE_CURRENT_BINARY_DIR}/gen_pipl.py") - set(PIPL_TOOL_DEPENDS "${PIPL_TOOL_PATH}") - file(WRITE "${PIPL_TOOL_PATH}" [=[ -#!/usr/bin/env python3 -import argparse -import json -import os -import struct - -def pad4(data: bytes) -> bytes: - remainder = len(data) % 4 - if remainder: - data += b'\x00' * (4 - remainder) - return data - -def make_property(vendor: bytes, key: bytes, prop_id: int, value: bytes) -> bytes: - return vendor + key + struct.pack('>I', prop_id) + struct.pack('>I', len(value)) + value - -def make_pipl(name: str, entry_point: str) -> bytes: - props = [ - make_property(b'ADBE', b'kind', 0, b'SPEA'), - make_property(b'ADBE', b'ivrs', 0, struct.pack('>I', 2)), - make_property(b'ADBE', b'mi32', 0, pad4(entry_point.encode('ascii'))), - make_property(b'ADBE', b'pinm', 0, pad4(name.encode('ascii'))), - ] - return struct.pack('>II', 0, len(props)) + b''.join(props) - -parser = argparse.ArgumentParser() -parser.add_argument("-input", required=True) -args = parser.parse_args() - -plugins = json.loads(args.input) -output_path = os.path.join(os.getcwd(), "plugin.pipl") -with open(output_path, "wb") as f: - f.write(struct.pack('>I', len(plugins))) - for desc in plugins: - f.write(make_pipl( - desc.get("name", "Plugin"), - desc.get("entry_point", "PluginMain"), - )) -]=]) - endif() - - if(PYTHON_EXECUTABLE AND EXISTS "${PIPL_TOOL_PATH}") - message(STATUS "Found Python: ${PYTHON_EXECUTABLE}") - message(STATUS "PiPL tool path: ${PIPL_TOOL_PATH}") - - # Generate plugin.pipl in build directory (pre-build) - set(PIPL_OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/plugin.pipl") - add_custom_command( - OUTPUT "${PIPL_OUTPUT}" - COMMAND ${PYTHON_EXECUTABLE} "${PIPL_TOOL_PATH}" - -input "[{\"name\":\"${PLUGIN_NAME}\", \"entry_point\":\"PluginMain\"}]" - WORKING_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}" - DEPENDS ${PIPL_TOOL_DEPENDS} - COMMENT "Generating PiPL with Python tool" - VERBATIM - ) - set_source_files_properties("${PIPL_OUTPUT}" PROPERTIES - GENERATED TRUE - MACOSX_PACKAGE_LOCATION "Resources/pipl" - ) - target_sources(${PROJECT_NAME} PRIVATE "${PIPL_OUTPUT}") - else() - message(WARNING "Python 3 not found; plugin.pipl will not be generated") - message(WARNING "PiPL resource is required for Illustrator to load the plugin") - endif() - - # ------------------------------------------------------------------------- - # .rsrc — Prefer a Rez-compiled resource fork when available - # ------------------------------------------------------------------------- - # Working Illustrator plugins on this machine ship a non-empty .rsrc file. - # Keep the synthetic empty resource fork only as a fallback for environments - # without Rez. + # Verified on-disk bundles load in Illustrator 2025 and 2026 with Rez-only + # resources, so package the .rsrc bundle resource directly. set(RSRC_FILE "${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_NAME}.rsrc") find_program(REZ_EXECUTABLE NAMES Rez @@ -847,7 +750,7 @@ message(STATUS " Deployment Target: ${CMAKE_OSX_DEPLOYMENT_TARGET}") if(REZ_EXECUTABLE) message(STATUS " Rez Compiler: ${REZ_EXECUTABLE}") else() -message(STATUS " Rez Compiler: NOT FOUND (PiPL will not be compiled)") +message(STATUS " Rez Compiler: NOT FOUND (.rsrc will not be compiled)") endif() endif() message(STATUS "") diff --git a/plugin/tools/pipl/create_pipl.py b/plugin/tools/pipl/create_pipl.py deleted file mode 100644 index 9563f21..0000000 --- a/plugin/tools/pipl/create_pipl.py +++ /dev/null @@ -1,42 +0,0 @@ -#!/usr/bin/env python3 - -import argparse -import json -import os -import sys - - -SCRIPT_DIR = os.path.dirname(os.path.abspath(__file__)) -if SCRIPT_DIR not in sys.path: - sys.path.insert(0, SCRIPT_DIR) - -from pipl_gen import pipl # noqa: E402 - - -def main() -> int: - parser = argparse.ArgumentParser() - parser.add_argument("-input", required=True, help="JSON plugin descriptor list") - parser.add_argument( - "-output", - default=os.path.join(os.getcwd(), "plugin.pipl"), - help="Output path for the generated PiPL binary", - ) - args = parser.parse_args() - - descriptors = json.loads(args.input) - if not isinstance(descriptors, list) or not descriptors: - raise ValueError("Expected a non-empty JSON array of plugin descriptors") - - descriptor = descriptors[0] - plugin_name = descriptor.get("name", "Plugin") - entry_point = descriptor.get("entry_point", "PluginMain") - - plugin = pipl() - plugin.add_plugin_name(plugin_name) - plugin.add_plugin_entry(entry_point) - plugin.generate_pipl_bin(args.output) - return 0 - - -if __name__ == "__main__": - raise SystemExit(main())