Skip to content
Open
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
36 changes: 36 additions & 0 deletions Scripts/build-phases/fail-non-debug-build-if-wormholy-present.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
#!/bin/bash -eu

# Checks if the SwiftPM setup includes Wormholy during a build that is not Debug.
#
# If that's the case, it fails the build, because we don't want the library in non Debug builds.
#
# See counterpart method remove_wormholy_dependency_from_package_swift in fastlane/Fastfile.

MODULES_PATH="${SRCROOT}/../Modules"
PACKAGE_PATH="${MODULES_PATH}/Package.swift"
# Note: when/if we'll remove the workspace, this will change to ${MODULES_PATH}/Package.resolved
PACKAGE_RESOLVED_PATH="${SRCROOT}/../WooCommerce.xcworkspace/xcshareddata/swiftpm/Package.resolved"

if [ "${CONFIGURATION}" == "Debug" ]; then
echo "info: Running in Debug build configuration. Skipping Wormholy check."
exit 0
else
echo "info: Running with a build configuration that is not Debug (${CONFIGURATION}). Checking Wormholy is not part of the SwiftPM setup..."
fi

EXPLANATION="You are likely running a distribution build from Xcode, which cannot strip Wormholy. Please build for distribution using Fastlane (\`bundle exec fastlane build_for_app_store_connect\` or \`bundle exec fastlane build_for_prototype_build\`)."

if grep --quiet "Wormholy" "${PACKAGE_PATH}"; then
echo "error: Wormholy reference found in Package.swift. $EXPLANATION"
exit 1
fi

# This is not necessary from the build point of view, because Package.swift is the source of truth.
# But checking it ensures that the packages were resolved before starting the build.

if grep --quiet "Wormholy" "${PACKAGE_RESOLVED_PATH}"; then
echo "error: Wormholy reference found in Package.resolved. $EXPLANATION"
exit 1
fi

echo "info: Wormholy not found in SwiftPM setup. Proceeding with the build..."
20 changes: 20 additions & 0 deletions WooCommerce/WooCommerce.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -14479,6 +14479,7 @@
isa = PBXNativeTarget;
buildConfigurationList = B56DB3E62049BFAA00D4AA8E /* Build configuration list for PBXNativeTarget "WooCommerce" */;
buildPhases = (
3F4278632E0BD6B700B11B9E /* Prevent Wormholy in non-Debug builds */,
57CCFFD4249D2A5700825FCF /* SwiftLint */,
3F50FE4528CAEE9F00C89201 /* Enforce AppLocalizedString usages */,
B56DB3C22049BFAA00D4AA8E /* Sources */,
Expand Down Expand Up @@ -15056,6 +15057,25 @@
/* End PBXResourcesBuildPhase section */

/* Begin PBXShellScriptBuildPhase section */
3F4278632E0BD6B700B11B9E /* Prevent Wormholy in non-Debug builds */ = {
isa = PBXShellScriptBuildPhase;
alwaysOutOfDate = 1;
buildActionMask = 2147483647;
files = (
);
inputFileListPaths = (
);
inputPaths = (
);
name = "Prevent Wormholy in non-Debug builds";
outputFileListPaths = (
);
outputPaths = (
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "${SRCROOT}/../Scripts/build-phases/fail-non-debug-build-if-wormholy-present.sh\n";
};
3F50FE4528CAEE9F00C89201 /* Enforce AppLocalizedString usages */ = {
isa = PBXShellScriptBuildPhase;
alwaysOutOfDate = 1;
Expand Down
33 changes: 33 additions & 0 deletions fastlane/Fastfile
Original file line number Diff line number Diff line change
Expand Up @@ -720,6 +720,8 @@ platform :ios do
lane :build_for_app_store_connect do |fetch_code_signing: true|
update_certs_and_profiles_app_store if fetch_code_signing

remove_wormholy_dependency_from_package_swift

gym(
scheme: 'WooCommerce',
workspace: WORKSPACE_PATH,
Expand Down Expand Up @@ -784,6 +786,8 @@ platform :ios do
lane :build_for_prototype_build do |fetch_code_signing: true|
update_certs_and_profiles_enterprise if fetch_code_signing

remove_wormholy_dependency_from_package_swift

Comment on lines +789 to +790
Copy link
Contributor

Choose a reason for hiding this comment

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

Do we want to remove it from Prototype Builds though?

My understanding is that Prototype Builds is exactly a case when Wormholy could be helpful for developers to debug network requests on a build without having to have to hook up Xcode when running the app to add breakpoints and intercept network responses, for example. So I'd assume that this is precisely one of the cases where they'd be interested in keeping it? (And only remove it for App Store submission)


Note: OTOH, I'm a bit wary of having a change (like the removal of Wormholy) being done only on the App Store builds if that means that it would never give us an opportunity to validate before the final release that this removal doesn't break the Release build… otherwise we'd risk not notice something breaking until the final release… or worse only after it has been submitted for review).
But I'm expecting that beta builds (i.e. TestFlight builds) would still allow us to catch those before the final release, even if Debug and Prototype Builds didn't have a change to validate earlier if a build that removed Wormholy would still compile.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Do we want to remove it from Prototype Builds though?

I had this configured only to strip on Release build, but the import has always been #if DEBUG so it wouldn't run in Prototype Builds anyway

image

26688b4#diff-9d849b4aa93181781d29c444d1280080fc0a011e503496222848ce2f5fcd5977R22-R24

Copy link
Contributor

Choose a reason for hiding this comment

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

Ah, I see, got it.

Could still be worth asking the dev team what they prefer though, as they're the one who will be using Wormholy for various debugging situations 🙃

Copy link
Contributor Author

Choose a reason for hiding this comment

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

cc @woocommerce/ios-developers for the question of whether to have Wormholy in the prototype build.

My two cents: It seems like a useful tool to have in an internal build.

build_number = ENV.fetch('BUILDKITE_BUILD_NUMBER', '0')
pr_or_branch = pull_request_number&.then { |num| "PR ##{num}" } || ENV.fetch('BUILDKITE_BRANCH', nil)

Expand Down Expand Up @@ -1555,3 +1559,32 @@ def report_milestone_error(error_title:)

buildkite_annotate(style: 'warning', context: 'error-with-milestone', message: error_message) if is_ci
end

# Removes all Wormholy entries from a given `Package.swift`
#
# Wormholy is a library we only need in Debug builds.
# We don't want to bloat other builds with it, despite how small it might be.
#
# Thanks to all our Swift dependencies being declared in `Modules/Package.swift`, removing Wormholy from the file safely removes if from Xcode.
# No need to edit the Xcode project.
#
# See counterpart build phase script fail-non-debug-build-if-wormholy-present.sh
def remove_wormholy_dependency_from_package_swift(
package_path: File.join(PROJECT_ROOT_FOLDER, 'Modules', 'Package.swift')
)
content = File.read(package_path)

# Remove any line containing Wormholy.
#
# This relies on Package.swift being straightforward in how it defines dependencies.
# Given the code is only for usage in this repo at this time, it seems safe to do.
#
# This also assumes no other dependency, target, etc. includes "Wormholy" in its name.
# That also seem safe.
cleaned_content = content.lines.reject do |line|
line.include? 'Wormholy'
end.join
Comment on lines +1579 to +1586
Copy link
Contributor

Choose a reason for hiding this comment

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

This might be ok for a first iteration of this, but still feels a bit fragile (especially in case one day we reformat the file to use multiline entries).

It's a shame that swift package command seems to have a dedicated add-dependency subcommand but no remove-dependency counterpart subcommand…

I also tried swift package dump-package to print the JSON representation and see if we could do something with it, but seems quite verbose and I'm not sure there's a way to regenerate a Package.swift from an altered JSON (i.e. after having removed Wormholy from the JSON)…


Maybe a safer alternative would be to encode the inclusion of the Wormholy package by some logic in the Swift code of the Package.swift file, like:

let includeWormholy = true

let package = Package(
   
    dependencies: [
       .package(url: , from: ),
       includeWormholy ? .package(url: "https://github.com/pmusolino/Wormholy", from: "2.0.0") : nil,
       .package(url: , from: ),
    ].compact
    
    // and same for the `.product` in the `.xcodeTarget` entry later in the file

Then this helper method in the Fastfile would just have to content.gsub('let includeWormholy = true', 'let includeWormholy = false').
That way this would reduce the risk in case we later refactor the Package.swift to use multiline statements, or DRY some of the .package(…) references into variables so we can reference them in multiple places in the Package(…) definition… by having that Fastlane method only change a single, clearly defined line, instead of relying on more brittle .include? logic…

WDYT?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

The let to control the insertion is neat.

I considered reading CONFIGURATION from the env, but it would not have worked because the package resolution happens outside of the process that populates it from the build settings.

To be honest though, the more I think of the approach to remove Wormholy this way the less I like doing so.

As @jaclync wrote in #15771

I doubt a debugging library would take up a lot of space though, I downloaded the zip and the whole folder including the demo folder took up 673 KB (852 KB on disk) with the Sources folder being 133 KB (233 KB on disk). I'd say let's go for it, and if app size proves to be an issue with the library contributing to it by a significant amount, we can remove it.

Maybe we'd be better off putting this on the shelf for the sake of keeping the process straightforward.

Copy link
Contributor

Choose a reason for hiding this comment

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

Yeah. Might be worth comparing the binary size with and without Wormholy (all other things the same) to have a definite answer of the size impact of the compiled lib in the final binary. But indeed the size difference might be insignificant enough to maybe prefer just keep it at all times after all, rather than trying to be clever and potentially introducing unforeseen side effect due to workaround…

Copy link
Contributor

@AliSoftware AliSoftware Jun 26, 2025

Choose a reason for hiding this comment

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

I tested building the Prototype Build with and without Wormholy while asking xcode to apply code thinning and generating IPA Size reports:

For iPhone17,5, iOS 18 Download Size Install Size
With Wormholy 97.22 MB 191.50 MB
Without Wormholy 97.09 MB 191.20 MB

cc @jaclync

How I got those numbers

1️⃣ Edit the Fastfile to ask for generating the app thinning reports when building Prototype Build:

--- a/fastlane/Fastfile
+++ b/fastlane/Fastfile
@@ -798,7 +798,8 @@ lane :build_for_prototype_build do |fetch_code_signing: true|
       export_options: {
         **COMMON_EXPORT_OPTIONS,
         method: 'enterprise',
-        iCloudContainerEnvironment: 'Production'
+        iCloudContainerEnvironment: 'Production',
+        thinning: '<thin-for-all-variants>'
       }
     )
   end

2️⃣ Run bundle exec fastlane build_for_prototype_build locally (with Wormholy still included in Modules/Package.swift), then copy the build/app-thinning.plist file to app-thinning-with-wormholy.plist

3️⃣ Comment the mentions of Wormholy in the Modules/Package.swift

--- a/Modules/Package.swift
+++ b/Modules/Package.swift
@@ -89,7 +89,7 @@ let package = Package(
         .package(url: "https://github.com/markiv/SwiftUI-Shimmer", from: "1.0.0"),
         .package(url: "https://github.com/nalexn/ViewInspector", from: "0.10.0"),
         .package(url: "https://github.com/onevcat/Kingfisher", from: "7.6.2"),
-        .package(url: "https://github.com/pmusolino/Wormholy", from: "2.0.0"),
+        // .package(url: "https://github.com/pmusolino/Wormholy", from: "2.0.0"),
         .package(url: "https://github.com/pavolkmet/ScrollViewSectionKit", from: "1.2.0"),
         .package(url: "https://github.com/Quick/Nimble.git", from: "13.0.0"),
         .package(url: "https://github.com/simibac/ConfettiSwiftUI.git", from: "1.0.0"),
@@ -388,7 +388,7 @@ enum XcodeSupport {
                     .product(name: "Shimmer", package: "SwiftUI-Shimmer"),
                     .product(name: "StripeTerminal", package: "stripe-terminal-ios"),
                     .product(name: "WordPressEditor", package: "AztecEditor-iOS"),
-                    .product(name: "Wormholy", package: "Wormholy"),
+                    // .product(name: "Wormholy", package: "Wormholy"),
                     .product(name: "ZendeskSupportSDK", package: "support_sdk_ios"),
                 ]
             ),

4️⃣ Run bundle exec fastlane build_for_prototype_build again, then copy the build/app-thinning.plist file to app-thinning-without-wormholy.plist

5️⃣ Compare the reported sizes. I chose to pick the entry for device: iPhone17,5, os-version: 18.0 as the baselime for that comparison, since that's the most recent iPhone model and version.

$ plutil -convert json -o - app-thinning-with-wormholy.plist | jq '.variants.[] | { downloadSize: .sizeCompressedApp, installSize: .sizeUncompressedApp, model: .variantDescriptors | select(.) | map(.device + " - " + .["os-version"] | select(. == "iPhone17,5 - 18.0")) | first } | select(.model)' 
{
  "downloadSize": 101946873,
  "installSize": 200801487,
  "model": "iPhone17,5 - 18.0"
}

$ plutil -convert json -o - app-thinning-without-wormholy.plist | jq '.variants.[] | { downloadSize: .sizeCompressedApp, installSize: .sizeUncompressedApp, model: .variantDescriptors | select(.) | map(.device + " - " + .["os-version"] | select(. == "iPhone17,5 - 18.0")) | first } | select(.model)'

{
  "downloadSize": 101810448,
  "installSize": 200491589,
  "model": "iPhone17,5 - 18.0"
}

(Apple doc reference)

Copy link
Contributor

Choose a reason for hiding this comment

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

For future reference, here's the complete App Thinning Reports (with and without Wormholy, plist and txt) I generated from my tests above:

App Thinning Reports.zip

Copy link
Contributor

Choose a reason for hiding this comment

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

Thanks a lot for figuring out a way to test the app size before/after @AliSoftware! I have to say the 0.13-0.3 MB increase is higher than I thought, especially since we already got an App Store warning about the app size and we're making effort to minimize the size.

WDYT about the approach suggested by Alex in p91TBi-diV-p2#comment-14321 to have a separate debug target where the library is included in? Or a separate release target that excludes the library, whichever is easier.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Also notice the discussion here: pmusolino/Wormholy#144

I tried this approach the other day and I could still see Wormholy in the build logs. But maybe I didn't configure the build settings properly.

Just leaving this here since I won't have the time to try it soon in case someone else wants to have a go.


File.write(package_path, cleaned_content)
UI.success("Stripped Wormholy from #{package_path}")
end
Loading