From 62a8c42a10734047af1ac126acfed1d054cb6677 Mon Sep 17 00:00:00 2001
From: James Borthwick <109382862+jamesrb1@users.noreply.github.com>
Date: Thu, 9 May 2024 11:37:11 -0700
Subject: [PATCH] Preprocessor to make select constructs public (#3880)
This PR allows the Paywalls Tester app to access some items internal to
purchases-ios by temporarily marking them public during complication.
Items that need to be public have been annotated with
`\\@PublicForExternalTesting`.
The Paywalls Tester project runs a script, Preprocessor.sh, as a
pre-action to add `public` to all classes/structs/enums/functions/inits
that have been annotated.
After compilation it runs a second script, Postprocessor.sh, as a
post-action to undo these changes so that they don't accidentally get
checked in.
The first script is run as a scheme pre-action rather than as a run
script build phase, because when run as a build phase the changes to the
files aren't picked up until the next compilation attempt.
It also changes the archive step to be built with a release
configuration, and removes the use of `@testable import` for debug
builds.
resolves PWL-459
---
...alOrIntroEligibilityChecker+TestData.swift | 8 +--
.../TrialOrIntroEligibilityChecker.swift | 1 +
.../Data/PaywallViewConfiguration.swift | 3 +
RevenueCatUI/Modifiers/ViewExtensions.swift | 1 +
RevenueCatUI/PaywallView.swift | 1 +
RevenueCatUI/Purchasing/PurchaseHandler.swift | 1 +
RevenueCatUI/View+PresentPaywallFooter.swift | 1 +
.../PaywallsTester.xcodeproj/project.pbxproj | 6 ++
.../PaywallsTester - Live Config.xcscheme | 38 +++++++++-
.../PaywallsTester - SK config.xcscheme | 36 ++++++++++
.../UI/Views/CustomPaywallContent.swift | 4 --
.../UI/Views/OfferingList/OfferingsList.swift | 4 --
.../UI/Views/PaywallPresenter.swift | 4 --
.../PaywallsTester/Postprocessor.sh | 52 ++++++++++++++
.../PaywallsTester/Preprocessor.sh | 69 +++++++++++++++++++
15 files changed, 211 insertions(+), 18 deletions(-)
create mode 100644 Tests/TestingApps/PaywallsTester/Postprocessor.sh
create mode 100644 Tests/TestingApps/PaywallsTester/Preprocessor.sh
diff --git a/RevenueCatUI/Data/IntroEligibility/TrialOrIntroEligibilityChecker+TestData.swift b/RevenueCatUI/Data/IntroEligibility/TrialOrIntroEligibilityChecker+TestData.swift
index 668b324de1..4b04f408c6 100644
--- a/RevenueCatUI/Data/IntroEligibility/TrialOrIntroEligibilityChecker+TestData.swift
+++ b/RevenueCatUI/Data/IntroEligibility/TrialOrIntroEligibilityChecker+TestData.swift
@@ -14,12 +14,11 @@
import Foundation
import RevenueCat
-#if DEBUG
-
@available(iOS 15.0, macOS 12.0, tvOS 15.0, *)
extension TrialOrIntroEligibilityChecker {
/// Creates a mock `TrialOrIntroEligibilityChecker` with a constant result.
+ // @PublicForExternalTesting
static func producing(eligibility: @autoclosure @escaping () -> IntroEligibilityStatus) -> Self {
return .init { packages in
return Dictionary(
@@ -34,7 +33,7 @@ extension TrialOrIntroEligibilityChecker {
)
}
}
-
+#if DEBUG
/// Creates a copy of this `TrialOrIntroEligibilityChecker` with a delay.
func with(delay seconds: TimeInterval) -> Self {
return .init { [checker = self.checker] in
@@ -43,7 +42,6 @@ extension TrialOrIntroEligibilityChecker {
return await checker($0)
}
}
+#endif
}
-
-#endif
diff --git a/RevenueCatUI/Data/IntroEligibility/TrialOrIntroEligibilityChecker.swift b/RevenueCatUI/Data/IntroEligibility/TrialOrIntroEligibilityChecker.swift
index 1ea2c1855c..072145c7d1 100644
--- a/RevenueCatUI/Data/IntroEligibility/TrialOrIntroEligibilityChecker.swift
+++ b/RevenueCatUI/Data/IntroEligibility/TrialOrIntroEligibilityChecker.swift
@@ -15,6 +15,7 @@ import Foundation
import RevenueCat
@available(iOS 13.0, tvOS 13.0, macOS 10.15, watchOS 6.2, *)
+// @PublicForExternalTesting
final class TrialOrIntroEligibilityChecker: ObservableObject {
typealias Checker = @Sendable ([Package]) async -> [Package: IntroEligibilityStatus]
diff --git a/RevenueCatUI/Data/PaywallViewConfiguration.swift b/RevenueCatUI/Data/PaywallViewConfiguration.swift
index da056c302b..b9e4adc505 100644
--- a/RevenueCatUI/Data/PaywallViewConfiguration.swift
+++ b/RevenueCatUI/Data/PaywallViewConfiguration.swift
@@ -11,6 +11,7 @@ import RevenueCat
/// Parameters needed to configure a ``PaywallView``.
@available(iOS 15.0, macOS 12.0, tvOS 15.0, watchOS 8.0, *)
+// @PublicForExternalTesting
struct PaywallViewConfiguration {
var content: Content
@@ -45,6 +46,7 @@ struct PaywallViewConfiguration {
extension PaywallViewConfiguration {
/// Offering selection for the paywall.
+ // @PublicForExternalTesting
enum Content {
case defaultOffering
@@ -60,6 +62,7 @@ extension PaywallViewConfiguration {
@available(iOS 15.0, macOS 12.0, tvOS 15.0, watchOS 8.0, *)
extension PaywallViewConfiguration {
+ // @PublicForExternalTesting
init(
offering: Offering? = nil,
customerInfo: CustomerInfo? = nil,
diff --git a/RevenueCatUI/Modifiers/ViewExtensions.swift b/RevenueCatUI/Modifiers/ViewExtensions.swift
index 4f83dc7c43..efc381cbde 100644
--- a/RevenueCatUI/Modifiers/ViewExtensions.swift
+++ b/RevenueCatUI/Modifiers/ViewExtensions.swift
@@ -86,6 +86,7 @@ extension View {
}
@ViewBuilder
+ // @PublicForExternalTesting
func scrollableIfNecessary(_ axis: Axis = .vertical, enabled: Bool = true) -> some View {
if enabled {
if #available(iOS 16.0, macOS 13.0, tvOS 16.0, watchOS 9.0, *) {
diff --git a/RevenueCatUI/PaywallView.swift b/RevenueCatUI/PaywallView.swift
index 8ebc2c47ab..b1d948bfe0 100644
--- a/RevenueCatUI/PaywallView.swift
+++ b/RevenueCatUI/PaywallView.swift
@@ -91,6 +91,7 @@ public struct PaywallView: View {
)
}
+ // @PublicForExternalTesting
init(configuration: PaywallViewConfiguration) {
self._introEligibility = .init(wrappedValue: configuration.introEligibility ?? .default())
self._purchaseHandler = .init(wrappedValue: configuration.purchaseHandler ?? .default())
diff --git a/RevenueCatUI/Purchasing/PurchaseHandler.swift b/RevenueCatUI/Purchasing/PurchaseHandler.swift
index 1b8473129d..c027cede6c 100644
--- a/RevenueCatUI/Purchasing/PurchaseHandler.swift
+++ b/RevenueCatUI/Purchasing/PurchaseHandler.swift
@@ -16,6 +16,7 @@ import StoreKit
import SwiftUI
@available(iOS 15.0, macOS 12.0, tvOS 15.0, watchOS 8.0, *)
+// @PublicForExternalTesting
final class PurchaseHandler: ObservableObject {
private let purchases: PaywallPurchasesType
diff --git a/RevenueCatUI/View+PresentPaywallFooter.swift b/RevenueCatUI/View+PresentPaywallFooter.swift
index ae5243be3d..29eb0bb804 100644
--- a/RevenueCatUI/View+PresentPaywallFooter.swift
+++ b/RevenueCatUI/View+PresentPaywallFooter.swift
@@ -181,6 +181,7 @@ extension View {
)
}
+ // @PublicForExternalTesting
func paywallFooter(
offering: Offering?,
customerInfo: CustomerInfo?,
diff --git a/Tests/TestingApps/PaywallsTester/PaywallsTester.xcodeproj/project.pbxproj b/Tests/TestingApps/PaywallsTester/PaywallsTester.xcodeproj/project.pbxproj
index 62c71c77d2..e42981a280 100644
--- a/Tests/TestingApps/PaywallsTester/PaywallsTester.xcodeproj/project.pbxproj
+++ b/Tests/TestingApps/PaywallsTester/PaywallsTester.xcodeproj/project.pbxproj
@@ -22,6 +22,7 @@
4FCA01FB2A3A1CBD00B262C0 /* StoreKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 4FCA01FA2A3A1CBD00B262C0 /* StoreKit.framework */; };
4FDF11202A7270F3004F3680 /* SamplePaywallsList.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4FDF111F2A7270F3004F3680 /* SamplePaywallsList.swift */; };
4FDF11222A72714C004F3680 /* SamplePaywalls.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4FDF11212A72714C004F3680 /* SamplePaywalls.swift */; };
+ 880B2AF22BEC2D62006B9393 /* Preprocessor.sh in Resources */ = {isa = PBXBuildFile; fileRef = 880B2AF12BEC2D62006B9393 /* Preprocessor.sh */; };
88B2F9882BE1943C00B43E0B /* ManagePaywallButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 88B2F9872BE1943C00B43E0B /* ManagePaywallButton.swift */; };
88B2F98B2BE19B1200B43E0B /* OfferingButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 88B2F98A2BE19B1200B43E0B /* OfferingButton.swift */; };
88B2F98D2BE3F1E900B43E0B /* TemplateInfo.swift in Sources */ = {isa = PBXBuildFile; fileRef = 88B2F98C2BE3F1E900B43E0B /* TemplateInfo.swift */; };
@@ -94,6 +95,8 @@
4FDF111F2A7270F3004F3680 /* SamplePaywallsList.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SamplePaywallsList.swift; sourceTree = ""; };
4FDF11212A72714C004F3680 /* SamplePaywalls.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SamplePaywalls.swift; sourceTree = ""; };
4FFD2A602AA154B4001F4B0C /* ar */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ar; path = ar.lproj/Localizable.strings; sourceTree = ""; };
+ 880B2AF12BEC2D62006B9393 /* Preprocessor.sh */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.script.sh; path = Preprocessor.sh; sourceTree = SOURCE_ROOT; };
+ 880B2AF32BEC35AA006B9393 /* Postprocessor.sh */ = {isa = PBXFileReference; lastKnownFileType = text.script.sh; path = Postprocessor.sh; sourceTree = SOURCE_ROOT; };
88B2F9872BE1943C00B43E0B /* ManagePaywallButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ManagePaywallButton.swift; sourceTree = ""; };
88B2F98A2BE19B1200B43E0B /* OfferingButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OfferingButton.swift; sourceTree = ""; };
88B2F98C2BE3F1E900B43E0B /* TemplateInfo.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TemplateInfo.swift; sourceTree = ""; };
@@ -206,6 +209,8 @@
children = (
4F34FF642A60ADBD00AADF11 /* Configuration.swift */,
4FDF11212A72714C004F3680 /* SamplePaywalls.swift */,
+ 880B2AF12BEC2D62006B9393 /* Preprocessor.sh */,
+ 880B2AF32BEC35AA006B9393 /* Postprocessor.sh */,
4FC882E02A5870C6005BE85E /* Info.plist */,
4FC046BF2A572E3700A28BCF /* Assets.xcassets */,
4FC046BD2A572E3700A28BCF /* PaywallsTester.entitlements */,
@@ -397,6 +402,7 @@
buildActionMask = 2147483647;
files = (
4F217A102A6DB6FB000B092D /* Assets.xcassets in Resources */,
+ 880B2AF22BEC2D62006B9393 /* Preprocessor.sh in Resources */,
4F4557E22A6FFE6A00160521 /* Localizable.strings in Resources */,
);
runOnlyForDeploymentPostprocessing = 0;
diff --git a/Tests/TestingApps/PaywallsTester/PaywallsTester.xcodeproj/xcshareddata/xcschemes/PaywallsTester - Live Config.xcscheme b/Tests/TestingApps/PaywallsTester/PaywallsTester.xcodeproj/xcshareddata/xcschemes/PaywallsTester - Live Config.xcscheme
index 42bf650ba3..0029172252 100644
--- a/Tests/TestingApps/PaywallsTester/PaywallsTester.xcodeproj/xcshareddata/xcschemes/PaywallsTester - Live Config.xcscheme
+++ b/Tests/TestingApps/PaywallsTester/PaywallsTester.xcodeproj/xcshareddata/xcschemes/PaywallsTester - Live Config.xcscheme
@@ -5,6 +5,42 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Tests/TestingApps/PaywallsTester/PaywallsTester.xcodeproj/xcshareddata/xcschemes/PaywallsTester - SK config.xcscheme b/Tests/TestingApps/PaywallsTester/PaywallsTester.xcodeproj/xcshareddata/xcschemes/PaywallsTester - SK config.xcscheme
index 1a3821cdfa..b7fddc519b 100644
--- a/Tests/TestingApps/PaywallsTester/PaywallsTester.xcodeproj/xcshareddata/xcschemes/PaywallsTester - SK config.xcscheme
+++ b/Tests/TestingApps/PaywallsTester/PaywallsTester.xcodeproj/xcshareddata/xcschemes/PaywallsTester - SK config.xcscheme
@@ -5,6 +5,42 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
"$log_file"
+
+# Find all .orig files and restore them
+find "$base_directory" -type f -name "*.swift.orig" | while read -r backup_file; do
+ original_file="${backup_file%.orig}"
+ cp "$backup_file" "$original_file"
+ rm -f "$backup_file"
+
+ echo "Restored: $original_file" | tee -a "$log_file"
+done
+
+echo "Undo process completed." | tee -a "$log_file"
diff --git a/Tests/TestingApps/PaywallsTester/Preprocessor.sh b/Tests/TestingApps/PaywallsTester/Preprocessor.sh
new file mode 100644
index 0000000000..36b59c05f2
--- /dev/null
+++ b/Tests/TestingApps/PaywallsTester/Preprocessor.sh
@@ -0,0 +1,69 @@
+#!/bin/sh
+
+# Preprocessor.sh
+# PaywallsTester
+#
+# Created by James Borthwick on 2024-05-08.
+#
+
+# Intended to be run via the scheme's pre-actions build phase
+#
+# This script searches up from ${PROJECT_DIR} to locate the RevenueCatUI directory.
+# Once found, it searches through all `.swift` files starting from that base directory.
+# It finds occurrences of `//@PublicForExternalTesting` and modifies the subsequent class, struct,
+# func, init, and enum declaration to make it public.
+
+find_dir() {
+ local dir="$1"
+ local target_dir="$2"
+ while [[ "$dir" != "/" ]]; do
+ if [[ -e "$dir/$target_dir" ]]; then
+ echo "$dir/$target_dir"
+ return 0
+ fi
+ dir=$(dirname "$dir") # go up one level
+ done
+ return 1 # Target directory not found
+}
+
+echo "Starting script to make items annotated with \`\/\/ @PublicForExternalTesting\` public."
+
+base_directory=$(find_dir "${PROJECT_DIR}" "RevenueCatUI")
+
+if [[ -z "$base_directory" ]]; then
+ echo "Error: RevenueCatUI not found in the current directory or any parent directory."
+ exit 1
+fi
+
+echo "Starting at: $base_directory"
+
+# debug log
+log_file="preprocess_log.txt"
+echo "Starting log at $(date)" > "$log_file"
+
+# Find all .swift files recursively from the base directory
+find "$base_directory" -type f -name "*.swift" | while read -r file; do
+
+ if grep -q '// @PublicForExternalTesting' "$file"; then
+ # Backup original file
+ backup_file="${file}.orig"
+ cp "$file" "$original_file"
+
+ # Find //@PublicForExternalTesting and replace it with public before declarations
+ sed -i.orig -E \
+ '/\/\/ @PublicForExternalTesting[[:space:]]*$/{
+ N
+ s/\/\/ @PublicForExternalTesting[[:space:]]*\n[[:space:]]*(static[[:space:]]+)?(struct|class|final[[:space:]]+class|enum|init|func)/public \1\2/
+ }' "$file"
+
+ # Log changes made to the file
+ diff_output=$(diff "$backup_file" "$file")
+ if [[ -n "$diff_output" ]]; then
+ echo "Changes made in file: $file" | tee -a "$log_file"
+ echo "$diff_output" | tee -a "$log_file"
+ fi
+
+ fi
+done
+
+echo "Processing completed." | tee -a "$log_file"