Skip to content

Commit 676c516

Browse files
authored
Check in CI that spm deinit takes the template app back to CocoaPods
## Summary `npx react-native spm deinit` takes an app from SwiftPM back to CocoaPods. No CI job tests this today. The unit tests mock `pod` and `xcodebuild`, and no job runs `deinit`. The CocoaPods jobs build apps that were never converted. A later change could break the way back without any failing check. This PR adds the full round trip to the SwiftPM new-app job (`test-ios-spm-newapp.yml`), in the Debug cell only. `deinit` does not depend on the flavor, and the job does not retry, so a failure shows up once, where people look for SwiftPM problems. 1. Before `spm add`, the job runs `assert-spm-deinit.sh record`. It saves the sha256 of `ios/Podfile` and `ios/.gitignore` (or `absent`), and fails at once if the Podfile is missing. The job also copies `ios/`. 2. After the SwiftPM build, the job runs `npx react-native spm deinit`. 3. The job prints `diff -r` between the copy and `ios/`. This is for information only and never fails the job. It also prints when a later step fails. 4. `assert-spm-deinit.sh check` then checks: - the Podfile is byte-identical; - `.gitignore` is byte-identical, or gone if it was absent before; - the `.spm-injected.json` marker and `ios/ReactNativeSPM/` are gone; - `project.pbxproj` has no SwiftPM package references, product dependencies, `ReactNativeSPM` paths, or SwiftPM build phases; - no shared scheme still has the SwiftPM pre-action. 5. The job runs `pod install` the same way as the HelloWorld CocoaPods job, then builds the workspace in Debug for the simulator. The Debug cell takes longer now, because it runs `pod install` and a second app build. The Release cell is unchanged. The check assumes the app had no Swift packages before `spm add`. The script header says so. ## Changelog: [INTERNAL] [ADDED] - CI check that `spm deinit` takes an app back to CocoaPods and that the app then builds ## Test Plan: I ran the script locally on copies of `private/helloworld/ios` with a test Podfile: - `record`: prints and writes both checksums; with no Podfile it fails with a clear error. - `check` after a real `deinit`: passes. - `check` with no `deinit`: fails with one error for each SwiftPM change still in place. - Wrong arguments: prints the usage and exits 2. - `bash -n` passes on the script, and the workflow YAML parses. Not run locally: shellcheck (not installed), and the workflow itself. CI on this PR runs it. Differential Revision: D130158963 Pull Request resolved: #59783
1 parent de05579 commit 676c516

2 files changed

Lines changed: 180 additions & 0 deletions

File tree

Lines changed: 128 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,128 @@
1+
#!/bin/bash
2+
# Copyright (c) Meta Platforms, Inc. and affiliates.
3+
#
4+
# This source code is licensed under the MIT license found in the
5+
# LICENSE file in the root directory of this source tree.
6+
7+
# Checks that `npx react-native spm deinit` left an iOS app with only its
8+
# CocoaPods files. It checks only what deinit removes; `pod install` restores
9+
# the CocoaPods references, so they are not checked here.
10+
#
11+
# It assumes the app had no Swift packages before `spm add`: any
12+
# packageReferences or packageProductDependencies left after deinit fail the
13+
# check. Do not use it on an app that already has Swift packages.
14+
#
15+
# Usage:
16+
# assert-spm-deinit.sh record <ios-dir>
17+
# Run before `spm add`. Prints PODFILE_SHA256 and GITIGNORE_SHA256, and
18+
# appends them to $GITHUB_ENV when it is set.
19+
# assert-spm-deinit.sh check <ios-dir> <name.xcodeproj> <podfile-sha256> <gitignore-sha256|absent>
20+
# Run after `spm deinit`, with the values that record printed.
21+
22+
usage() {
23+
echo "Usage: $0 record <ios-dir>"
24+
echo " $0 check <ios-dir> <name.xcodeproj> <podfile-sha256> <gitignore-sha256|absent>"
25+
exit 2
26+
}
27+
28+
sha256_or_absent() {
29+
if [ -f "$1" ]; then
30+
shasum -a 256 "$1" | cut -d ' ' -f 1
31+
else
32+
echo absent
33+
fi
34+
}
35+
36+
record() {
37+
local ios_dir=$1
38+
if [ ! -f "$ios_dir/Podfile" ]; then
39+
echo "::error::$ios_dir/Podfile is missing. Record the checksums while the app is still on CocoaPods."
40+
exit 1
41+
fi
42+
43+
local values
44+
values="PODFILE_SHA256=$(sha256_or_absent "$ios_dir/Podfile")
45+
GITIGNORE_SHA256=$(sha256_or_absent "$ios_dir/.gitignore")"
46+
echo "$values"
47+
if [ -n "$GITHUB_ENV" ]; then
48+
echo "$values" >> "$GITHUB_ENV"
49+
fi
50+
}
51+
52+
check() {
53+
local ios_dir=$1
54+
local xcodeproj="$ios_dir/$2"
55+
local expected_podfile_sha256=$3
56+
local expected_gitignore_sha256=$4
57+
local pbxproj="$xcodeproj/project.pbxproj"
58+
local failed=0
59+
60+
fail() {
61+
echo "::error::$1"
62+
failed=1
63+
}
64+
65+
local actual_podfile_sha256
66+
actual_podfile_sha256=$(sha256_or_absent "$ios_dir/Podfile")
67+
if [ "$actual_podfile_sha256" != "$expected_podfile_sha256" ]; then
68+
fail "Podfile changed: sha256 is '$actual_podfile_sha256', expected '$expected_podfile_sha256'."
69+
fi
70+
71+
local actual_gitignore_sha256
72+
actual_gitignore_sha256=$(sha256_or_absent "$ios_dir/.gitignore")
73+
if [ "$actual_gitignore_sha256" != "$expected_gitignore_sha256" ]; then
74+
fail ".gitignore changed: sha256 is '$actual_gitignore_sha256', expected '$expected_gitignore_sha256'."
75+
fi
76+
77+
if [ -e "$xcodeproj/.spm-injected.json" ]; then
78+
fail "$xcodeproj/.spm-injected.json is still there."
79+
fi
80+
81+
if [ -e "$ios_dir/ReactNativeSPM" ]; then
82+
fail "$ios_dir/ReactNativeSPM is still there."
83+
fi
84+
85+
if [ ! -f "$pbxproj" ]; then
86+
fail "$pbxproj is missing."
87+
else
88+
local injected
89+
for injected in \
90+
XCLocalSwiftPackageReference \
91+
XCSwiftPackageProductDependency \
92+
packageReferences \
93+
packageProductDependencies \
94+
ReactNativeSPM \
95+
"Sync SPM Autolinking" \
96+
"Embed React Native Flavored Frameworks"; do
97+
if grep -qF "$injected" "$pbxproj"; then
98+
fail "$pbxproj still contains '$injected'."
99+
fi
100+
done
101+
fi
102+
103+
local scheme
104+
for scheme in "$xcodeproj"/xcshareddata/xcschemes/*.xcscheme; do
105+
if [ -f "$scheme" ] && grep -qF 'title = "Sync SPM Autolinking"' "$scheme"; then
106+
fail "$scheme still has the Sync SPM Autolinking pre-action."
107+
fi
108+
done
109+
110+
if [ $failed -ne 0 ]; then
111+
exit 1
112+
fi
113+
echo "spm deinit removed every SwiftPM change."
114+
}
115+
116+
case "$1" in
117+
record)
118+
[ $# -eq 2 ] || usage
119+
record "$2"
120+
;;
121+
check)
122+
[ $# -eq 5 ] || usage
123+
check "$2" "$3" "$4" "$5"
124+
;;
125+
*)
126+
usage
127+
;;
128+
esac

‎.github/workflows/test-ios-spm-newapp.yml‎

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,14 @@ jobs:
4646
uses: ./.github/actions/setup-spm-artifacts
4747
with:
4848
working-directory: ${{ env.APP_IOS_DIR }}
49+
# The `spm deinit` round trip runs in the Debug cell only: it does not
50+
# depend on the flavor.
51+
- name: Record HelloWorld before SwiftPM
52+
if: matrix.flavor == 'Debug'
53+
shell: bash
54+
run: |
55+
./.github/workflow-scripts/assert-spm-deinit.sh record "$APP_IOS_DIR"
56+
cp -R "$APP_IOS_DIR" "$RUNNER_TEMP/ios-before-spm"
4957
# HelloWorld is a CocoaPods project in git. CI converts it to SwiftPM on every run.
5058
- name: Convert HelloWorld to SwiftPM
5159
shell: bash
@@ -100,3 +108,47 @@ jobs:
100108
echo "::error::Release build embeds a Debug React.framework ($COUNT getDebugProps symbols, expected none)"
101109
exit 1
102110
fi
111+
- name: Take HelloWorld back to CocoaPods
112+
if: matrix.flavor == 'Debug'
113+
shell: bash
114+
working-directory: ${{ env.APP_IOS_DIR }}
115+
run: npx react-native spm deinit
116+
# Informational only. Expected differences include the CocoaPods
117+
# references and the .xcworkspace, which `pod install` restores, and the
118+
# JavaScriptCore.framework reference and empty Pods group, which deinit
119+
# does not restore on purpose.
120+
- name: Show what changed since before SwiftPM
121+
if: ${{ !cancelled() && matrix.flavor == 'Debug' }}
122+
shell: bash
123+
run: diff -r -x build "$RUNNER_TEMP/ios-before-spm" "$APP_IOS_DIR" || true
124+
- name: Check that only the CocoaPods files are left
125+
if: matrix.flavor == 'Debug'
126+
shell: bash
127+
run: ./.github/workflow-scripts/assert-spm-deinit.sh check "$APP_IOS_DIR" "$XCODE_PROJECT" "$PODFILE_SHA256" "$GITIGNORE_SHA256"
128+
- name: Setup ruby
129+
if: matrix.flavor == 'Debug'
130+
uses: ruby/setup-ruby@v1
131+
with:
132+
ruby-version: 2.6.10
133+
# The tarballs are the Debug ones that setup-spm-artifacts downloaded.
134+
- name: Install the CocoaPods dependencies
135+
if: matrix.flavor == 'Debug'
136+
shell: bash
137+
working-directory: private/helloworld
138+
run: |
139+
export RCT_USE_LOCAL_RN_DEP=/tmp/deps-debug/ReactNativeDependenciesDebug.xcframework.tar.gz
140+
export RCT_TESTONLY_RNCORE_TARBALL_PATH=/tmp/rc-debug/ReactCoreDebug.xcframework.tar.gz
141+
yarn bootstrap ios | cat
142+
- name: Build ${{ env.XCODE_SCHEME }} with CocoaPods (Debug)
143+
if: matrix.flavor == 'Debug'
144+
shell: bash
145+
working-directory: ${{ env.APP_IOS_DIR }}
146+
run: |
147+
xcodebuild \
148+
-workspace "${XCODE_PROJECT%.xcodeproj}.xcworkspace" \
149+
-scheme "$XCODE_SCHEME" \
150+
-configuration Debug \
151+
-sdk iphonesimulator \
152+
-destination 'generic/platform=iOS Simulator' \
153+
-derivedDataPath build/cocoapods-e2e-dd \
154+
build

0 commit comments

Comments
 (0)