iosxcodecocoapodsxcode-cloud

Xcode Cloud project with CocoaPods shell script builds locally but fails on cloud


I am currently testing Xcode Cloud as a member of Apple's private beta program and encountered an issue when trying to Archive / Build (any action) on the cloud for my project.

The project is a fairly basic SwiftUI app with CocoaPods dependencies. I have followed the steps to integrate CocoaPods into my project as described by Apple by simply committing my Pods directory to GitHub. However, I am getting a build error on the cloud for every attempted action:

Command PhaseScriptExecution failed with a nonzero exit code

Here is the ASC log for reference:

Xcode Cloud error log

This is very strange because the same project builds and archives successfully on my local machine. I have used the same macOS and Xcode versions in the Workflow editor as my local version of Xcode.

How can I resolve this error?


Solution

  • Custom shell scripts restrictions

    TL;DR

    Apple has locked down the security of their hosted infrastructure by only enabling shell scripts to run as part of ci_post_clone.sh, ci_pre_xcodebuild.sh or ci_post_xcodebuild.sh in the ci_scripts folder. Your Pods project has a custom shell script outside of this folder that is not triggered by one of these CI scripts, so does not have running permissions.

    The solution for this specific issue are:

    1. (technically) Refactor your build script phase to inline the shell script file inside the run script.
    2. (recommended, but not always possible) Use the Swift Package version of the CocoaPod if available.
    3. (workaround) Downgrade the CocoaPod to a version without an external shell script.

    Reference

    From Customize your advanced Xcode Cloud workflows:

    If your script doesn't appear to be running when you expect it to, double-check that you've named it correctly and placed it in a ci_scripts folder alongside your project.

    ...

    Lastly, it should be noted that in a test action, multiple environments are used to build and run your tests. Only the environment that is used for building your tests will have your source code cloned into it by default. The environments that run your tests won't have source code cloned into them. They'll only have the ci_scripts folder made available on them. As a result, the post-clone script won't run in these environments and your custom scripts and any of their dependencies, such as other shell scripts and small tools, must be entirely contained within the ci_scripts folder.

    Build script phases ARE allowed to run user-defined code as part of the build process, however, we can only run inlined custom scripts here. As discussed, external shell scripts have restricted permissions. Running the external shell script file after moving to ci_scripts does NOT work. e.g. "${PODS_ROOT}/../ci_scripts/AppCenter-xcframeworks.sh".

    Although not relevant here, note that the environment that tests your project won't have the source code cloned into them.

    For tests or other environments to reference custom script files, we need to store additional scripts inside the ci_scripts folder to ensure the action has access to it. Apple only allows 3 scripts to run corresponding to 3 stages of a build:

    1. After cloning the source code into the build environment.
    2. Before running xcodebuild.
    3. After running xcodebuild.

    Additional shell scripts can can ONLY run here after delegating from the respective ci_post_clone.sh, ci_pre_xcodebuild.sh or ci_post_xcodebuild.sh files in the ci_scripts folder.

    Solution 1

    My issue was running an external shell script during the build process. Apple does allow Run Script Build Phases in Xcode Cloud workflows, but they have to be inlined. So, I had to do 4 steps to run a custom Pod shell script as part of a build phase:

    1. Refactor your Build Phases -> Run Script Phase script to inline the shell script file.
    2. Check the project builds locally after clearing DerivedData.
    3. Commit your code to GitHub / VCS.
    4. Trigger the workflow.

    Xcode Pods Shell Script refactor 1

    Solution 2 (recommended, but not always possible)

    Add the Swift Package version of the CocoaPod as a dependency following Apple's documentation.

    Solution 3 (workaround)

    Downgrade your CocoaPod to a version without external shell scripts.

    Notes

    As you can tell from the amount of effort required to workaround custom shell script build phases with Xcode Cloud, I suggest raising an issue on the specific CocoaPod repository to migrate away from custom shell script files. These kinds of steps make using Xcode Cloud very painful.

    As Xcode Cloud adoption grows it is entirely possible that individual CocoaPods no longer reference custom shell script files. I don't see Apple opening up their infrastructure to enable arbitrary shell script execution because this is a security risk, and to be honest, should have been prevented on other CI providers too.

    I can also see how hassles like these to include legacy CocoaPods dependencies could accelerate more projects to migrate to SPM. SPM is already popular, and will likely become more popular as Apple ensures first-class integration.

    Disclaimer: Xcode Cloud is in private beta so this issue may be resolved in future versions if shell script permissions are relaxed...