Adds tooling to generate and test an iOS XCframework, in a way that will also facilitate adding other XCframework targets for other Apple platforms (tvOS, watchOS, visionOS and even macOS, potentially). --------- Co-authored-by: Hugo van Kemenade <1324225+hugovk@users.noreply.github.com>
138 lines
6.5 KiB
Bash
Executable File
138 lines
6.5 KiB
Bash
Executable File
# Utility methods for use in an Xcode project.
|
|
#
|
|
# An iOS XCframework cannot include any content other than the library binary
|
|
# and relevant metadata. However, Python requires a standard library at runtime.
|
|
# Therefore, it is necessary to add a build step to an Xcode app target that
|
|
# processes the standard library and puts the content into the final app.
|
|
#
|
|
# In general, these tools will be invoked after bundle resources have been
|
|
# copied into the app, but before framework embedding (and signing).
|
|
#
|
|
# The following is an example script, assuming that:
|
|
# * Python.xcframework is in the root of the project
|
|
# * There is an `app` folder that contains the app code
|
|
# * There is an `app_packages` folder that contains installed Python packages.
|
|
# -----
|
|
# set -e
|
|
# source $PROJECT_DIR/Python.xcframework/build/build_utils.sh
|
|
# install_python Python.xcframework app app_packages
|
|
# -----
|
|
|
|
# Copy the standard library from the XCframework into the app bundle.
|
|
#
|
|
# Accepts one argument:
|
|
# 1. The path, relative to the root of the Xcode project, where the Python
|
|
# XCframework can be found.
|
|
install_stdlib() {
|
|
PYTHON_XCFRAMEWORK_PATH=$1
|
|
|
|
mkdir -p "$CODESIGNING_FOLDER_PATH/python/lib"
|
|
if [ "$EFFECTIVE_PLATFORM_NAME" = "-iphonesimulator" ]; then
|
|
echo "Installing Python modules for iOS Simulator"
|
|
if [ -d "$PROJECT_DIR/$PYTHON_XCFRAMEWORK_PATH/ios-arm64-simulator" ]; then
|
|
SLICE_FOLDER="ios-arm64-simulator"
|
|
else
|
|
SLICE_FOLDER="ios-arm64_x86_64-simulator"
|
|
fi
|
|
else
|
|
echo "Installing Python modules for iOS Device"
|
|
SLICE_FOLDER="ios-arm64"
|
|
fi
|
|
|
|
# If the XCframework has a shared lib folder, then it's a full framework.
|
|
# Copy both the common and slice-specific part of the lib directory.
|
|
# Otherwise, it's a single-arch framework; use the "full" lib folder.
|
|
if [ -d "$PROJECT_DIR/$PYTHON_XCFRAMEWORK_PATH/lib" ]; then
|
|
rsync -au --delete "$PROJECT_DIR/$PYTHON_XCFRAMEWORK_PATH/lib/" "$CODESIGNING_FOLDER_PATH/python/lib/"
|
|
rsync -au "$PROJECT_DIR/$PYTHON_XCFRAMEWORK_PATH/$SLICE_FOLDER/lib-$ARCHS/" "$CODESIGNING_FOLDER_PATH/python/lib/"
|
|
else
|
|
rsync -au --delete "$PROJECT_DIR/$PYTHON_XCFRAMEWORK_PATH/$SLICE_FOLDER/lib/" "$CODESIGNING_FOLDER_PATH/python/lib/"
|
|
fi
|
|
}
|
|
|
|
# Convert a single .so library into a framework that iOS can load.
|
|
#
|
|
# Accepts three arguments:
|
|
# 1. The path, relative to the root of the Xcode project, where the Python
|
|
# XCframework can be found.
|
|
# 2. The base path, relative to the installed location in the app bundle, that
|
|
# needs to be processed. Any .so file found in this path (or a subdirectory
|
|
# of it) will be processed.
|
|
# 2. The full path to a single .so file to process. This path should include
|
|
# the base path.
|
|
install_dylib () {
|
|
PYTHON_XCFRAMEWORK_PATH=$1
|
|
INSTALL_BASE=$2
|
|
FULL_EXT=$3
|
|
|
|
# The name of the extension file
|
|
EXT=$(basename "$FULL_EXT")
|
|
# The location of the extension file, relative to the bundle
|
|
RELATIVE_EXT=${FULL_EXT#$CODESIGNING_FOLDER_PATH/}
|
|
# The path to the extension file, relative to the install base
|
|
PYTHON_EXT=${RELATIVE_EXT/$INSTALL_BASE/}
|
|
# The full dotted name of the extension module, constructed from the file path.
|
|
FULL_MODULE_NAME=$(echo $PYTHON_EXT | cut -d "." -f 1 | tr "/" ".");
|
|
# A bundle identifier; not actually used, but required by Xcode framework packaging
|
|
FRAMEWORK_BUNDLE_ID=$(echo $PRODUCT_BUNDLE_IDENTIFIER.$FULL_MODULE_NAME | tr "_" "-")
|
|
# The name of the framework folder.
|
|
FRAMEWORK_FOLDER="Frameworks/$FULL_MODULE_NAME.framework"
|
|
|
|
# If the framework folder doesn't exist, create it.
|
|
if [ ! -d "$CODESIGNING_FOLDER_PATH/$FRAMEWORK_FOLDER" ]; then
|
|
echo "Creating framework for $RELATIVE_EXT"
|
|
mkdir -p "$CODESIGNING_FOLDER_PATH/$FRAMEWORK_FOLDER"
|
|
cp "$PROJECT_DIR/$PYTHON_XCFRAMEWORK_PATH/build/$PLATFORM_FAMILY_NAME-dylib-Info-template.plist" "$CODESIGNING_FOLDER_PATH/$FRAMEWORK_FOLDER/Info.plist"
|
|
plutil -replace CFBundleExecutable -string "$FULL_MODULE_NAME" "$CODESIGNING_FOLDER_PATH/$FRAMEWORK_FOLDER/Info.plist"
|
|
plutil -replace CFBundleIdentifier -string "$FRAMEWORK_BUNDLE_ID" "$CODESIGNING_FOLDER_PATH/$FRAMEWORK_FOLDER/Info.plist"
|
|
fi
|
|
|
|
echo "Installing binary for $FRAMEWORK_FOLDER/$FULL_MODULE_NAME"
|
|
mv "$FULL_EXT" "$CODESIGNING_FOLDER_PATH/$FRAMEWORK_FOLDER/$FULL_MODULE_NAME"
|
|
# Create a placeholder .fwork file where the .so was
|
|
echo "$FRAMEWORK_FOLDER/$FULL_MODULE_NAME" > ${FULL_EXT%.so}.fwork
|
|
# Create a back reference to the .so file location in the framework
|
|
echo "${RELATIVE_EXT%.so}.fwork" > "$CODESIGNING_FOLDER_PATH/$FRAMEWORK_FOLDER/$FULL_MODULE_NAME.origin"
|
|
|
|
echo "Signing framework as $EXPANDED_CODE_SIGN_IDENTITY_NAME ($EXPANDED_CODE_SIGN_IDENTITY)..."
|
|
/usr/bin/codesign --force --sign "$EXPANDED_CODE_SIGN_IDENTITY" ${OTHER_CODE_SIGN_FLAGS:-} -o runtime --timestamp=none --preserve-metadata=identifier,entitlements,flags --generate-entitlement-der "$CODESIGNING_FOLDER_PATH/$FRAMEWORK_FOLDER"
|
|
}
|
|
|
|
# Process all the dynamic libraries in a path into Framework format.
|
|
#
|
|
# Accepts two arguments:
|
|
# 1. The path, relative to the root of the Xcode project, where the Python
|
|
# XCframework can be found.
|
|
# 2. The base path, relative to the installed location in the app bundle, that
|
|
# needs to be processed. Any .so file found in this path (or a subdirectory
|
|
# of it) will be processed.
|
|
process_dylibs () {
|
|
PYTHON_XCFRAMEWORK_PATH=$1
|
|
LIB_PATH=$2
|
|
find "$CODESIGNING_FOLDER_PATH/$LIB_PATH" -name "*.so" | while read FULL_EXT; do
|
|
install_dylib $PYTHON_XCFRAMEWORK_PATH "$LIB_PATH/" "$FULL_EXT"
|
|
done
|
|
}
|
|
|
|
# The entry point for post-processing a Python XCframework.
|
|
#
|
|
# Accepts 1 or more arguments:
|
|
# 1. The path, relative to the root of the Xcode project, where the Python
|
|
# XCframework can be found. If the XCframework is in the root of the project,
|
|
# 2+. The path of a package, relative to the root of the packaged app, that contains
|
|
# library content that should be processed for binary libraries.
|
|
install_python() {
|
|
PYTHON_XCFRAMEWORK_PATH=$1
|
|
shift
|
|
|
|
install_stdlib $PYTHON_XCFRAMEWORK_PATH
|
|
PYTHON_VER=$(ls -1 "$CODESIGNING_FOLDER_PATH/python/lib")
|
|
echo "Install Python $PYTHON_VER standard library extension modules..."
|
|
process_dylibs $PYTHON_XCFRAMEWORK_PATH python/lib/$PYTHON_VER/lib-dynload
|
|
|
|
for package_path in $@; do
|
|
echo "Installing $package_path extension modules ..."
|
|
process_dylibs $PYTHON_XCFRAMEWORK_PATH $package_path
|
|
done
|
|
}
|