Skip to content

Commit

Permalink
Preprocessor to make select constructs public (#3880)
Browse files Browse the repository at this point in the history
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.

<img width="669" alt="image"
src="https://github.com/RevenueCat/purchases-ios/assets/109382862/0b0bacdb-79de-441d-8d61-2a5dd03b1b53">

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
  • Loading branch information
jamesrb1 authored May 9, 2024
1 parent 453d1ca commit 62a8c42
Show file tree
Hide file tree
Showing 15 changed files with 211 additions and 18 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand All @@ -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
Expand All @@ -43,7 +42,6 @@ extension TrialOrIntroEligibilityChecker {
return await checker($0)
}
}
#endif

}

#endif
Original file line number Diff line number Diff line change
Expand Up @@ -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]
Expand Down
3 changes: 3 additions & 0 deletions RevenueCatUI/Data/PaywallViewConfiguration.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -45,6 +46,7 @@ struct PaywallViewConfiguration {
extension PaywallViewConfiguration {

/// Offering selection for the paywall.
// @PublicForExternalTesting
enum Content {

case defaultOffering
Expand All @@ -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,
Expand Down
1 change: 1 addition & 0 deletions RevenueCatUI/Modifiers/ViewExtensions.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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, *) {
Expand Down
1 change: 1 addition & 0 deletions RevenueCatUI/PaywallView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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())
Expand Down
1 change: 1 addition & 0 deletions RevenueCatUI/Purchasing/PurchaseHandler.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
1 change: 1 addition & 0 deletions RevenueCatUI/View+PresentPaywallFooter.swift
Original file line number Diff line number Diff line change
Expand Up @@ -181,6 +181,7 @@ extension View {
)
}

// @PublicForExternalTesting
func paywallFooter(
offering: Offering?,
customerInfo: CustomerInfo?,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 */; };
Expand Down Expand Up @@ -94,6 +95,8 @@
4FDF111F2A7270F3004F3680 /* SamplePaywallsList.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SamplePaywallsList.swift; sourceTree = "<group>"; };
4FDF11212A72714C004F3680 /* SamplePaywalls.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SamplePaywalls.swift; sourceTree = "<group>"; };
4FFD2A602AA154B4001F4B0C /* ar */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ar; path = ar.lproj/Localizable.strings; sourceTree = "<group>"; };
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 = "<group>"; };
88B2F98A2BE19B1200B43E0B /* OfferingButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OfferingButton.swift; sourceTree = "<group>"; };
88B2F98C2BE3F1E900B43E0B /* TemplateInfo.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TemplateInfo.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -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 */,
Expand Down Expand Up @@ -397,6 +402,7 @@
buildActionMask = 2147483647;
files = (
4F217A102A6DB6FB000B092D /* Assets.xcassets in Resources */,
880B2AF22BEC2D62006B9393 /* Preprocessor.sh in Resources */,
4F4557E22A6FFE6A00160521 /* Localizable.strings in Resources */,
);
runOnlyForDeploymentPostprocessing = 0;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,42 @@
<BuildAction
parallelizeBuildables = "YES"
buildImplicitDependencies = "YES">
<PreActions>
<ExecutionAction
ActionType = "Xcode.IDEStandardExecutionActionsCore.ExecutionActionType.ShellScriptAction">
<ActionContent
title = "Run Script"
scriptText = "#!/bin/bash&#10;&#10;bash &quot;${PROJECT_DIR}/Preprocessor.sh&quot;&#10;">
<EnvironmentBuildable>
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "4F6BED992A26A64200CD9322"
BuildableName = "PaywallsTester.app"
BlueprintName = "PaywallsTester"
ReferencedContainer = "container:PaywallsTester.xcodeproj">
</BuildableReference>
</EnvironmentBuildable>
</ActionContent>
</ExecutionAction>
</PreActions>
<PostActions>
<ExecutionAction
ActionType = "Xcode.IDEStandardExecutionActionsCore.ExecutionActionType.ShellScriptAction">
<ActionContent
title = "Run Script"
scriptText = "# Type a script or drag a script file from your workspace to insert its path.&#10;&#10;bash &quot;${PROJECT_DIR}/Postprocessor.sh&quot;&#10;">
<EnvironmentBuildable>
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "4F6BED992A26A64200CD9322"
BuildableName = "PaywallsTester.app"
BlueprintName = "PaywallsTester"
ReferencedContainer = "container:PaywallsTester.xcodeproj">
</BuildableReference>
</EnvironmentBuildable>
</ActionContent>
</ExecutionAction>
</PostActions>
<BuildActionEntries>
<BuildActionEntry
buildForTesting = "YES"
Expand Down Expand Up @@ -70,7 +106,7 @@
buildConfiguration = "Debug">
</AnalyzeAction>
<ArchiveAction
buildConfiguration = "Debug"
buildConfiguration = "Release"
revealArchiveInOrganizer = "YES">
</ArchiveAction>
</Scheme>
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,42 @@
<BuildAction
parallelizeBuildables = "YES"
buildImplicitDependencies = "YES">
<PreActions>
<ExecutionAction
ActionType = "Xcode.IDEStandardExecutionActionsCore.ExecutionActionType.ShellScriptAction">
<ActionContent
title = "Run Script"
scriptText = "#!/bin/bash&#10;&#10;bash &quot;${PROJECT_DIR}/Preprocessor.sh&quot;&#10;">
<EnvironmentBuildable>
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "4F6BED992A26A64200CD9322"
BuildableName = "PaywallsTester.app"
BlueprintName = "PaywallsTester"
ReferencedContainer = "container:PaywallsTester.xcodeproj">
</BuildableReference>
</EnvironmentBuildable>
</ActionContent>
</ExecutionAction>
</PreActions>
<PostActions>
<ExecutionAction
ActionType = "Xcode.IDEStandardExecutionActionsCore.ExecutionActionType.ShellScriptAction">
<ActionContent
title = "Run Script"
scriptText = "# Type a script or drag a script file from your workspace to insert its path.&#10;&#10;bash &quot;${PROJECT_DIR}/Postprocessor.sh&quot;&#10;">
<EnvironmentBuildable>
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "4F6BED992A26A64200CD9322"
BuildableName = "PaywallsTester.app"
BlueprintName = "PaywallsTester"
ReferencedContainer = "container:PaywallsTester.xcodeproj">
</BuildableReference>
</EnvironmentBuildable>
</ActionContent>
</ExecutionAction>
</PostActions>
<BuildActionEntries>
<BuildActionEntry
buildForTesting = "YES"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,7 @@
// Created by Nacho Soto on 8/25/23.
//

#if DEBUG
@testable import RevenueCatUI
#else
import RevenueCatUI
#endif

import SwiftUI

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,7 @@
//

import RevenueCat
#if DEBUG
@testable import RevenueCatUI
#else
import RevenueCatUI
#endif
import SwiftUI

struct OfferingsList: View {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,7 @@

import SwiftUI
import RevenueCat
#if DEBUG
@testable import RevenueCatUI
#else
import RevenueCatUI
#endif

struct PaywallPresenter: View {

Expand Down
52 changes: 52 additions & 0 deletions Tests/TestingApps/PaywallsTester/Postprocessor.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
#!/bin/sh

# Preprocessor.sh
# PaywallsTester
#
# Created by James Borthwick on 2024-05-08.
#

# Intended to be run via the scheme's post-actions build phase
#
# This script undoes the changes made by "Preprocessor.sh" so that they don't
# end up accidentally checked in.


echo "Starting undo process."

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
}

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="undo_log.txt"
echo "Starting log at $(date)" > "$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"
69 changes: 69 additions & 0 deletions Tests/TestingApps/PaywallsTester/Preprocessor.sh
Original file line number Diff line number Diff line change
@@ -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"

0 comments on commit 62a8c42

Please sign in to comment.