Skip to content

[Bug]: iOS Symlinks to plugins should be relative #8704

Open

Description

@piotr-cz

Capacitor Version

Capacitor Doctor 💊

Latest Dependencies:

@capacitor/cli: 8.6.0
@capacitor/core: 8.6.0
@capacitor/android: 8.6.0
@capacitor/ios: 8.6.0

Installed Dependencies:

@capacitor/core: 8.6.0
@capacitor/cli: 8.6.0
@capacitor/android: 8.6.0
@capacitor/ios: 8.6.0

[success] iOS looking great! 👌
[success] Android looking great! 👌

Other API Details

npm --version output: 11.19.0
node --version output: v24.21.0

Platforms Affected

  • iOS
  • Android
  • Web

Current Behavior

With the experimental SPM packageOptions introduced in #8471, setting symlink: true for a plugin makes npx cap sync ios create a symlink under ios/App/CapApp-SPM/symlinks/. The symlink target is an absolute path to the plugin in node_modules:

$ readlink ios/App/CapApp-SPM/symlinks/CapacitorFirebaseMessaging
/Users/<me>/<path-to-project>/node_modules/@capacitor-firebase/messaging

Because the absolute path is specific to one machine, the project is no longer portable:
SwiftPM package resolution then fails because the symlink points to a path that doesn't exist.

Expected Behavior

The symlink target should be relative to the symlink's own directory, e.g.:

$ readlink ios/App/CapApp-SPM/symlinks/CapacitorFirebaseMessaging
../../../../node_modules/@capacitor-firebase/messaging

This would work on any machine and with any checkout location, as long as node_modules is installed.

Project Reproduction

  1. Create a Capacitor 8.4+ app using SPM for iOS.
  2. npm install @capacitor-firebase/messaging
  3. Add to capacitor.config.json:
        {
          "experimental": {
            "ios": {
              "spm": {
                "packageOptions": {
                  "@capacitor-firebase/messaging": { "symlink": true }
                }
              }
            }
          }
        }
  4. npx cap sync ios
  5. find ios -type l -lname '/*' lists the symlink, and readlink shows an absolute target.

Additional Information

Suggested fix: compute the target relative to the link's directory before creating the link, e.g.

  if (symlink) {
-   await ensureSymlink(plugin.rootPath, resolve(config.ios.nativeProjectDirAbs, 'CapApp-SPM', symlinkFolder));
+   const linkPath = resolve(config.ios.nativeProjectDirAbs, 'CapApp-SPM', symlinkFolder);
+   await ensureSymlink(relative(dirname(linkPath), plugin.rootPath), linkPath);
  }

Note on implementation: if the link is created with fs-extra's ensureSymlink (directly or via @ionic/utils-fs), passing a relative srcPath used to fail on re-runs when the symlink already existed. That was fixed in fs-extra 11.3.4 (jprichardson/node-fs-extra#1038, jprichardson/node-fs-extra#1064). So switching to a relative target should be paired with requiring fs-extra >= 11.3.4, otherwise subsequent cap sync runs could fail.

Current workaround:

  • gitignoring ios/App/CapApp-SPM/symlinks/ and relying on cap sync to recreate the links
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    Fields

    Stage

    None yet

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions