Skip to content

Add a script to install Apple certificate for CI iOS jobs #4703

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 20 commits into from
Aug 26, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 33 additions & 0 deletions .ci/scripts/setup-ios.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
#!/bin/bash
# Copyright (c) Meta Platforms, Inc. and affiliates.
# All rights reserved.
#
# This source code is licensed under the BSD-style license found in the
# LICENSE file in the root directory of this source tree.

set -exu

# This script follows the instructions from GitHub to install an Apple certificate
# https://docs.github.com/en/actions/use-cases-and-examples/deploying/installing-an-apple-certificate-on-macos-runners-for-xcode-development

CERTIFICATE_PATH="${RUNNER_TEMP}"/build_certificate.p12
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How is the $RUNNER_TEMP set?

Copy link
Contributor Author

@huydhn huydhn Aug 26, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's one of the default env variables set by GitHub, usually it's a sub-directory inside runner installation dir https://docs.github.com/en/actions/writing-workflows/choosing-what-your-workflow-does/store-information-in-variables#default-environment-variables

PP_PATH="${RUNNER_TEMP}"/build_pp.mobileprovision
KEYCHAIN_PATH="${RUNNER_TEMP}"/app-signing.keychain-db

# Import certificate and provisioning profile from secrets
echo -n "$BUILD_CERTIFICATE_BASE64" | base64 --decode -o $CERTIFICATE_PATH
echo -n "$BUILD_PROVISION_PROFILE_BASE64" | base64 --decode -o $PP_PATH

# Create a temporary keychain
security create-keychain -p "$KEYCHAIN_PASSWORD" $KEYCHAIN_PATH
security set-keychain-settings -lut 21600 $KEYCHAIN_PATH
security unlock-keychain -p "$KEYCHAIN_PASSWORD" $KEYCHAIN_PATH

# Import certificate to the keychain
security import $CERTIFICATE_PATH -P "" -A -t cert -f pkcs12 -k $KEYCHAIN_PATH
security set-key-partition-list -S apple-tool:,apple: -k "$KEYCHAIN_PASSWORD" $KEYCHAIN_PATH
security list-keychain -d user -s $KEYCHAIN_PATH

# Apply provisioning profile
mkdir -p ~/Library/MobileDevice/Provisioning\ Profiles
cp $PP_PATH ~/Library/MobileDevice/Provisioning\ Profiles
14 changes: 13 additions & 1 deletion .github/workflows/apple.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ on:
pull_request:
paths:
- .ci/docker/**
- .ci/scripts/setup-ios.sh
- .github/workflows/apple.yml
- install_requirements.sh
- backends/apple/**
Expand All @@ -27,24 +28,35 @@ jobs:
test-demo-ios:
name: test-demo-ios
uses: pytorch/test-infra/.github/workflows/macos_job.yml@main
secrets: inherit
with:
runner: macos-latest-xlarge
python-version: '3.11'
submodules: 'true'
ref: ${{ github.event_name == 'pull_request' && github.event.pull_request.head.sha || github.sha }}
timeout: 90
secrets-env: BUILD_CERTIFICATE_BASE64 EXECUTORCH_DEMO_BUILD_PROVISION_PROFILE_BASE64 KEYCHAIN_PASSWORD
upload-artifact: ios-apps
script: |
BUILD_TOOL=cmake

.ci/scripts/setup-conda.sh

# Setup Apple certificate for iOS development
BUILD_PROVISION_PROFILE_BASE64="${SECRET_EXECUTORCH_DEMO_BUILD_PROVISION_PROFILE_BASE64}" \
BUILD_CERTIFICATE_BASE64="${SECRET_BUILD_CERTIFICATE_BASE64}" \
KEYCHAIN_PASSWORD="${SECRET_KEYCHAIN_PASSWORD}" \
.ci/scripts/setup-ios.sh

# Setup MacOS dependencies as there is no Docker support on MacOS atm
GITHUB_RUNNER=1 PYTHON_EXECUTABLE=python ${CONDA_RUN} --no-capture-output \
.ci/scripts/setup-macos.sh "${BUILD_TOOL}"

export ARTIFACTS_DIR_NAME=artifacts-to-be-uploaded

# Build and test iOS Demo App
PYTHON_EXECUTABLE=python ${CONDA_RUN} --no-capture-output \
build/test_ios_ci.sh
build/test_ios_ci.sh ${ARTIFACTS_DIR_NAME}

build-frameworks-ios:
name: build-frameworks-ios
Expand Down
49 changes: 49 additions & 0 deletions build/test_ios_ci.sh
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,9 @@ APP_PATH="examples/demo-apps/apple_ios/ExecuTorchDemo/ExecuTorchDemo"
MODEL_NAME="mv3"
SIMULATOR_NAME="executorch"

# If this is set, copy the build artifacts to this directory
ARTIFACTS_DIR_NAME="$1"

finish() {
EXIT_STATUS=$?
if xcrun simctl list | grep -q "$SIMULATOR_NAME"; then
Expand Down Expand Up @@ -64,3 +67,49 @@ xcodebuild test \
-project "$APP_PATH.xcodeproj" \
-scheme MobileNetClassifierTest \
-destination name="$SIMULATOR_NAME"

# NB: https://docs.aws.amazon.com/devicefarm/latest/developerguide/test-types-ios-xctest-ui.html
say "Package The Test Suite"

xcodebuild build-for-testing \
-project "$APP_PATH.xcodeproj" \
-scheme MobileNetClassifierTest \
-destination platform="iOS" \
-allowProvisioningUpdates \
DEVELOPMENT_TEAM=78E7V7QP35 \
CODE_SIGN_STYLE=Manual \
PROVISIONING_PROFILE_SPECIFIER=ExecuTorchDemo \
CODE_SIGN_IDENTITY="iPhone Distribution"

# The hack to figure out where the xctest package locates
BUILD_DIR=$(xcodebuild -showBuildSettings -project "$APP_PATH.xcodeproj" -json | jq -r ".[0].buildSettings.BUILD_DIR")

# Prepare the demo app
MODE="Debug"
PLATFORM="iphoneos"
pushd "${BUILD_DIR}/${MODE}-${PLATFORM}"

rm -rf Payload && mkdir Payload
MOCK_APP_NAME=ExecuTorchDemo

ls -lah
cp -r "${MOCK_APP_NAME}.app" Payload && zip -vr "${MOCK_APP_NAME}.ipa" Payload

popd

# Prepare the test suite
pushd "${BUILD_DIR}"

ls -lah
zip -vr "${MOCK_APP_NAME}.xctestrun.zip" *.xctestrun

popd

if [[ -n "${ARTIFACTS_DIR_NAME}" ]]; then
mkdir -p "${ARTIFACTS_DIR_NAME}"
# Prepare all the artifacts to upload
cp "${BUILD_DIR}/${MODE}-${PLATFORM}/${MOCK_APP_NAME}.ipa" "${ARTIFACTS_DIR_NAME}/"
cp "${BUILD_DIR}/${MOCK_APP_NAME}.xctestrun.zip" "${ARTIFACTS_DIR_NAME}/"

ls -lah "${ARTIFACTS_DIR_NAME}/"
fi
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,13 @@
remoteGlobalIDString = 03C818302AC79FCD0084CC29;
remoteInfo = ImageClassification;
};
84EF1FE92C7850B6005922B4 /* PBXContainerItemProxy */ = {
Copy link
Contributor Author

@huydhn huydhn Aug 23, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All the changes here in xcodeproj are to make the ExecuTorchDemo app host the test suite MobileNetClassifierTest so that the suite can be run on actual AWS devices (coming up next in a separate PR)

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What do you mean by making the ExecuTorchDemo app host the test suite MobileNetClassifierTest? Are they built and uploaded as separate artifacts in test_ios_ci.sh?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So, unlike running the test suite in a simulator, running it on actual iOS devices requires a hosting app. So I have 2 choices:

  1. Either create a new empty app to host MobileNetClassifierTest
  2. or just reuse the ExecuTorchDemo app (as show in my xcode screenshot). This kind of makes more sense to me because we will also be able to test if the app can be run at all on iOS. So, two birds one stone.

Screenshot 2024-08-26 at 11 37 27

@shoumikhin what would be your preferred choice here from the expert?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Btw, the change I'm highlighting in the screenshot is responsible for those magic numbers that you notice (auto-generated by xcode)

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For this PR to demonstrate using the certs in the workflow I think option 2 makes sense. I'm chatting with @shoumikhin regarding a generic app for benchmarking we will create a new app from scratch with no UI but just tests. @huydhn @shoumikhin once we have that app, can we reuse the provisioning profile and secret directly, or need to re-gen for the new app?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

From what I see, if we can keep the bundle id org.pytorch.executorch.demo.test, then we can reuse the provisioning profile. Otherwise, we need a new one and store it under a different secret.

isa = PBXContainerItemProxy;
containerPortal = 032C01672AC228E5002955E1 /* Project object */;
proxyType = 1;
remoteGlobalIDString = 032C016E2AC228E6002955E1;
Comment on lines +64 to +66
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What are these magic numbers?

remoteInfo = App;
};
/* End PBXContainerItemProxy section */

/* Begin PBXCopyFilesBuildPhase section */
Expand Down Expand Up @@ -330,6 +337,7 @@
buildRules = (
);
dependencies = (
84EF1FEA2C7850B6005922B4 /* PBXTargetDependency */,
);
name = MobileNetClassifierTest;
packageProductDependencies = (
Expand Down Expand Up @@ -489,6 +497,11 @@
target = 03C818302AC79FCD0084CC29 /* ImageClassification */;
targetProxy = 03C818452AC7A0DB0084CC29 /* PBXContainerItemProxy */;
};
84EF1FEA2C7850B6005922B4 /* PBXTargetDependency */ = {
isa = PBXTargetDependency;
target = 032C016E2AC228E6002955E1 /* App */;
targetProxy = 84EF1FE92C7850B6005922B4 /* PBXContainerItemProxy */;
};
/* End PBXTargetDependency section */

/* Begin XCBuildConfiguration section */
Expand Down Expand Up @@ -633,7 +646,7 @@
INFOPLIST_KEY_UIRequiresFullScreen = YES;
INFOPLIST_KEY_UISupportedInterfaceOrientations = UIInterfaceOrientationPortrait;
MARKETING_VERSION = 1.0;
PRODUCT_BUNDLE_IDENTIFIER = org.pytorch.executorch.demo;
PRODUCT_BUNDLE_IDENTIFIER = org.pytorch.executorch.demo.test;
PRODUCT_NAME = "$(PROJECT_NAME)";
PROVISIONING_PROFILE_SPECIFIER = "";
SUPPORTED_PLATFORMS = "iphoneos iphonesimulator";
Expand Down Expand Up @@ -661,7 +674,7 @@
INFOPLIST_KEY_UIRequiresFullScreen = YES;
INFOPLIST_KEY_UISupportedInterfaceOrientations = UIInterfaceOrientationPortrait;
MARKETING_VERSION = 1.0;
PRODUCT_BUNDLE_IDENTIFIER = org.pytorch.executorch.demo;
PRODUCT_BUNDLE_IDENTIFIER = org.pytorch.executorch.demo.test;
PRODUCT_NAME = "$(PROJECT_NAME)";
PROVISIONING_PROFILE_SPECIFIER = "";
SUPPORTED_PLATFORMS = "iphoneos iphonesimulator";
Expand Down Expand Up @@ -703,6 +716,7 @@
SUPPORTS_MACCATALYST = NO;
SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO;
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/ExecuTorchDemo.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/ExecuTorchDemo";
};
name = Debug;
};
Expand All @@ -717,6 +731,7 @@
PRODUCT_NAME = "$(TARGET_NAME)";
SUPPORTS_MACCATALYST = NO;
SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO;
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/ExecuTorchDemo.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/ExecuTorchDemo";
};
name = Release;
};
Expand Down
Loading