diff --git a/.circleci/config.yml b/.circleci/config.yml index 4ca98e8693..9569cc04ac 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -22,7 +22,7 @@ release-tags: &release-tags version: 2.1 commands: - install-gems: + install-dependencies: parameters: directory: type: string @@ -40,6 +40,10 @@ commands: key: v1-gem-cache-{{ checksum "Gemfile.lock" }} paths: - vendor/bundle + - run: + name: Install swiftlint + command: brew install swiftlint + scan-and-archive: parameters: directory: @@ -65,12 +69,12 @@ commands: command: | bundle exec fastlane archive - install-gems-scan-and-archive: + install-dependencies-scan-and-archive: parameters: directory: type: string steps: - - install-gems: + - install-dependencies: directory: << parameters.directory >> - scan-and-archive: directory: << parameters.directory >> @@ -87,16 +91,15 @@ commands: update-spm-integration-commit: steps: - - install-gems + - install-dependencies - run: - name: Update git commit in SPM package - working_directory: IntegrationTests/SPMIntegration/ + name: Update git commit in targets that use SPM for dependencies command: | bundle exec fastlane update_swift_package_commit update-carthage-integration-commit: steps: - - install-gems + - install-dependencies - run: name: Update git commit in Carthage Integration tests working_directory: IntegrationTests/CarthageIntegration/ @@ -112,7 +115,7 @@ jobs: steps: - checkout - - install-gems + - install-dependencies - run: name: Run tests @@ -134,7 +137,7 @@ jobs: - run: name: Install swiftlint command: brew install swiftlint - - install-gems + - install-dependencies - run: name: Build AppleTV, WatchOS, and macOS command: bundle exec fastlane build_tv_watch_mac @@ -158,15 +161,15 @@ jobs: - checkout - run: name: Open simulator - command: xcrun instruments -w "iPhone 11 Pro (14.2) [" || true + command: xcrun instruments -w "iPhone 11 Pro (14.5) [" || true - - install-gems + - install-dependencies - run: name: Run StoreKit Tests command: bundle exec fastlane storekit_tests environment: - SCAN_DEVICE: iPhone 11 Pro (14.2) + SCAN_DEVICE: iPhone 11 Pro (14.5) - store_test_results: path: fastlane/test_output - store_artifacts: @@ -213,7 +216,7 @@ jobs: shell: /bin/bash --login -o pipefail steps: - checkout - - install-gems + - install-dependencies - run: name: Build docs command: bundle exec fastlane run jazzy @@ -247,7 +250,7 @@ jobs: shell: /bin/bash --login -o pipefail steps: - checkout - - install-gems + - install-dependencies - trust-github-key - run: name: Prepare next version @@ -261,8 +264,8 @@ jobs: steps: - checkout - - install-gems - - install-gems: + - install-dependencies + - install-dependencies: directory: IntegrationTests/CocoapodsIntegration - run: @@ -283,7 +286,7 @@ jobs: - checkout - trust-github-key - update-spm-integration-commit - - install-gems-scan-and-archive: + - install-dependencies-scan-and-archive: directory: IntegrationTests/SPMIntegration/ integration-tests-carthage: @@ -313,7 +316,7 @@ jobs: paths: - Carthage - - install-gems-scan-and-archive: + - install-dependencies-scan-and-archive: directory: IntegrationTests/CarthageIntegration/ integration-tests-xcode-direct-integration: @@ -324,7 +327,7 @@ jobs: steps: - checkout - - install-gems-scan-and-archive: + - install-dependencies-scan-and-archive: directory: IntegrationTests/XcodeDirectIntegration/ lint: @@ -334,9 +337,7 @@ jobs: shell: /bin/bash --login -o pipefail steps: - checkout - - run: - name: Install swiftlint - command: brew install swiftlint + - install-dependencies - run: name: Run fastlane swiftlint lane command: fastlane run swiftlint raise_if_swiftlint_error:true strict:true diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md index a843b4b21d..a3fb956712 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.md +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -1,9 +1,10 @@ --- name: Bug report about: Filling a bug report -title: "" +title: '' labels: bug -assignees: "" +assignees: '' + --- - [ ] I have updated Purchases SDK to the latest version diff --git a/.github/ISSUE_TEMPLATE/custom.md b/.github/ISSUE_TEMPLATE/custom.md new file mode 100644 index 0000000000..183fa1fbde --- /dev/null +++ b/.github/ISSUE_TEMPLATE/custom.md @@ -0,0 +1,11 @@ +--- +name: Swift Migration Task +about: For use when creating issues for any work relating to our Swift migration. +title: '' +labels: swift migration +assignees: '' + +--- + +Please add screenshots, links to code, any relevant information, and then add any relevant tag, e.g.: +"Test backfill" for when more tests are needed, or "you can do this" for things that don't need any extra context and are good issues for new folks to help out with. diff --git a/.github/ISSUE_TEMPLATE/migration_task.md b/.github/ISSUE_TEMPLATE/migration_task.md deleted file mode 100644 index a12ddf37f2..0000000000 --- a/.github/ISSUE_TEMPLATE/migration_task.md +++ /dev/null @@ -1,3 +0,0 @@ -Please add screenshots, links to code, any relevant information, and then add any relevant tag, e.g.: -"Test backfill" for when more tests are needed, or "you can do this" for things that don't need any -extra context and are good issues for new folks to help out with. diff --git a/.jazzy.yaml b/.jazzy.yaml index b8fe3c7a5c..af89192658 100644 --- a/.jazzy.yaml +++ b/.jazzy.yaml @@ -5,9 +5,9 @@ objc: true sdk: iphonesimulator module: Purchases umbrella_header: Purchases/Public/Purchases.h -module_version: 3.12.0-SNAPSHOT +module_version: 3.13.0-SNAPSHOT github_url: https://github.com/revenuecat/purchases-ios -github_file_prefix: https://github.com/revenuecat/purchases-ios/tree/3.12.0-SNAPSHOT +github_file_prefix: https://github.com/revenuecat/purchases-ios/tree/3.13.0-SNAPSHOT output: docs # Leaving this commented out. We used to specify this before, but now it's working without it # xcodebuild_arguments: [--objc,Purchases/Public/Purchases.h,--,-x,objective-c,-isysroot,$(xcrun --show-sdk-path),-I,$(pwd)] diff --git a/.swiftlint.yml b/.swiftlint.yml index 24a1a7ddac..890c3f710c 100644 --- a/.swiftlint.yml +++ b/.swiftlint.yml @@ -8,6 +8,7 @@ excluded: - StoreKitTests - PublicSDKAPITester - vendor + - scan_derived_data disabled_rules: - trailing_comma - todo @@ -22,4 +23,4 @@ opt_in_rules: identifier_name: max_length: warning: 60 - error: 80 \ No newline at end of file + error: 80 diff --git a/.version b/.version index e7a6e31c99..2336b05c7d 100644 --- a/.version +++ b/.version @@ -1 +1 @@ -3.12.0-SNAPSHOT \ No newline at end of file +3.13.0-SNAPSHOT \ No newline at end of file diff --git a/CHANGELOG.latest.md b/CHANGELOG.latest.md index bf7917fdfc..76dd471816 100644 --- a/CHANGELOG.latest.md +++ b/CHANGELOG.latest.md @@ -1,4 +1,2 @@ -- Updates log message for `createAlias` to improve clarity - https://github.com/RevenueCat/purchases-ios/pull/498 -- Adds `rc_` to all Foundation extensions to prevent name collisions - https://github.com/RevenueCat/purchases-ios/pull/500 +- Fixed an issue in some versions of Xcode where compiling would fail with `Definition conflicts with previous value` in `ETagManager.swift` + https://github.com/revenuecat/purchases-ios/pull/659 diff --git a/CHANGELOG.md b/CHANGELOG.md index b4402c7ea5..b1e79752b2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,67 @@ +## 3.12.1 +- Fixed an issue in some versions of Xcode where compiling would fail with `Definition conflicts with previous value` in `ETagManager.swift` + https://github.com/revenuecat/purchases-ios/pull/659 + +## 3.12.0 + +### Identity V3: + +#### New methods +- Introduces `logIn`, a new way of identifying users, which also returns whether a new user has been registered in the system. +`logIn` uses a new backend endpoint. +- Introduces `logOut`, a replacement for `reset`. + +#### Deprecations +- deprecates `createAlias` in favor of `logIn` +- deprecates `identify` in favor of `logIn` +- deprecates `reset` in favor of `logOut` +- deprecates `allowSharingAppStoreAccount` in favor of dashboard-side configuration + + https://github.com/RevenueCat/purchases-ios/pull/453 + https://github.com/RevenueCat/purchases-ios/pull/438 + https://github.com/RevenueCat/purchases-ios/pull/506 + + +### Other changes: + +#### Public additions +##### SharedPurchases nullability +- Fixed `sharedPurchases` nullability +- Introduced new property, `isConfigured`, that can be used to check whether the SDK has been configured and `sharedPurchases` won't be `nil`. + https://github.com/RevenueCat/purchases-ios/pull/508 + +##### Improved log handling +- Added new property `logLevel`, which provides more granular settings for the log level. Valid values are `debug`, `info`, `warn` and `error`. +- Added new method, `setLogHandler`, which allows developers to use their own code to handle logging, and integrate their existing systems. + https://github.com/RevenueCat/purchases-ios/pull/481 + https://github.com/RevenueCat/purchases-ios/pull/515 + + +#### Deprecations +- Deprecated `debugLogsEnabled` property in favor of `LogLevel`. Use `Purchases.logLevel = .debug` as a replacement. + +#### Other + +- Fixed CI issues with creating pull requests + https://github.com/RevenueCat/purchases-ios/pull/504 +- Improved Github Issues bot behavior + https://github.com/RevenueCat/purchases-ios/pull/507 +- Added e-tags to reduce network traffic usage + https://github.com/RevenueCat/purchases-ios/pull/509 +- Fixed a warning in Xcode 13 with an outdated path in Package.swift + https://github.com/RevenueCat/purchases-ios/pull/522 +- Switched to Swift Package Manager for handling dependencies for test targets. + https://github.com/RevenueCat/purchases-ios/pull/527 +- Removed all `fatalError`s from the codebase + https://github.com/RevenueCat/purchases-ios/pull/529 + https://github.com/RevenueCat/purchases-ios/pull/527 +- Updated link for error message when UserDefaults are deleted outside the SDK + https://github.com/RevenueCat/purchases-ios/pull/531 +- Improved many of the templates and added `CODE_OF_CONDUCT.md` to make contributing easier + https://github.com/RevenueCat/purchases-ios/pull/534 + https://github.com/RevenueCat/purchases-ios/pull/537 + https://github.com/RevenueCat/purchases-ios/pull/589 + ## 3.11.1 - Updates log message for `createAlias` to improve clarity https://github.com/RevenueCat/purchases-ios/pull/498 diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md new file mode 100644 index 0000000000..0904d3481d --- /dev/null +++ b/CODE_OF_CONDUCT.md @@ -0,0 +1,133 @@ + +# Contributor Covenant Code of Conduct + +## Our Pledge + +We as members, contributors, and leaders pledge to make participation in our +community a harassment-free experience for everyone, regardless of age, body +size, visible or invisible disability, ethnicity, sex characteristics, gender +identity and expression, level of experience, education, socio-economic status, +nationality, personal appearance, race, caste, color, religion, or sexual identity +and orientation. + +We pledge to act and interact in ways that contribute to an open, welcoming, +diverse, inclusive, and healthy community. + +## Our Standards + +Examples of behavior that contributes to a positive environment for our +community include: + +* Demonstrating empathy and kindness toward other people +* Being respectful of differing opinions, viewpoints, and experiences +* Giving and gracefully accepting constructive feedback +* Accepting responsibility and apologizing to those affected by our mistakes, + and learning from the experience +* Focusing on what is best not just for us as individuals, but for the + overall community + +Examples of unacceptable behavior include: + +* The use of sexualized language or imagery, and sexual attention or + advances of any kind +* Trolling, insulting or derogatory comments, and personal or political attacks +* Public or private harassment +* Publishing others' private information, such as a physical or email + address, without their explicit permission +* Other conduct which could reasonably be considered inappropriate in a + professional setting + +## Enforcement Responsibilities + +Community leaders are responsible for clarifying and enforcing our standards of +acceptable behavior and will take appropriate and fair corrective action in +response to any behavior that they deem inappropriate, threatening, offensive, +or harmful. + +Community leaders have the right and responsibility to remove, edit, or reject +comments, commits, code, wiki edits, issues, and other contributions that are +not aligned to this Code of Conduct, and will communicate reasons for moderation +decisions when appropriate. + +## Scope + +This Code of Conduct applies within all community spaces, and also applies when +an individual is officially representing the community in public spaces. +Examples of representing our community include using an official e-mail address, +posting via an official social media account, or acting as an appointed +representative at an online or offline event. + +## Enforcement + +Instances of abusive, harassing, or otherwise unacceptable behavior may be +reported to the community leaders responsible for enforcement at +support@revenuecat.com. +All complaints will be reviewed and investigated promptly and fairly. + +All community leaders are obligated to respect the privacy and security of the +reporter of any incident. + +## Enforcement Guidelines + +Community leaders will follow these Community Impact Guidelines in determining +the consequences for any action they deem in violation of this Code of Conduct: + +### 1. Correction + +**Community Impact**: Use of inappropriate language or other behavior deemed +unprofessional or unwelcome in the community. + +**Consequence**: A private, written warning from community leaders, providing +clarity around the nature of the violation and an explanation of why the +behavior was inappropriate. A public apology may be requested. + +### 2. Warning + +**Community Impact**: A violation through a single incident or series +of actions. + +**Consequence**: A warning with consequences for continued behavior. No +interaction with the people involved, including unsolicited interaction with +those enforcing the Code of Conduct, for a specified period of time. This +includes avoiding interactions in community spaces as well as external channels +like social media. Violating these terms may lead to a temporary or +permanent ban. + +### 3. Temporary Ban + +**Community Impact**: A serious violation of community standards, including +sustained inappropriate behavior. + +**Consequence**: A temporary ban from any sort of interaction or public +communication with the community for a specified period of time. No public or +private interaction with the people involved, including unsolicited interaction +with those enforcing the Code of Conduct, is allowed during this period. +Violating these terms may lead to a permanent ban. + +### 4. Permanent Ban + +**Community Impact**: Demonstrating a pattern of violation of community +standards, including sustained inappropriate behavior, harassment of an +individual, or aggression toward or disparagement of classes of individuals. + +**Consequence**: A permanent ban from any sort of public interaction within +the community. + +## Attribution + +This Code of Conduct is adapted from the [Contributor Covenant][homepage], +version 2.0, available at +[https://www.contributor-covenant.org/version/2/0/code_of_conduct.html][v2.0]. + +Community Impact Guidelines were inspired by +[Mozilla's code of conduct enforcement ladder][Mozilla CoC]. + +For answers to common questions about this code of conduct, see the FAQ at +[https://www.contributor-covenant.org/faq][FAQ]. Translations are available +at [https://www.contributor-covenant.org/translations][translations]. + +[homepage]: https://www.contributor-covenant.org +[v2.0]: https://www.contributor-covenant.org/version/2/0/code_of_conduct.html +[Mozilla CoC]: https://github.com/mozilla/diversity +[FAQ]: https://www.contributor-covenant.org/faq +[translations]: https://www.contributor-covenant.org/translations diff --git a/Examples/MagicWeather/MagicWeather.xcodeproj/project.pbxproj b/Examples/MagicWeather/MagicWeather.xcodeproj/project.pbxproj index 550d90c394..612d6c1541 100644 --- a/Examples/MagicWeather/MagicWeather.xcodeproj/project.pbxproj +++ b/Examples/MagicWeather/MagicWeather.xcodeproj/project.pbxproj @@ -451,8 +451,8 @@ isa = XCRemoteSwiftPackageReference; repositoryURL = "https://github.com/RevenueCat/purchases-ios.git"; requirement = { - kind = upToNextMajorVersion; - minimumVersion = 3.12.0-SNAPSHOT; + branch = main; + kind = branch; }; }; /* End XCRemoteSwiftPackageReference section */ diff --git a/Examples/MagicWeather/MagicWeather.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/Examples/MagicWeather/MagicWeather.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index 280ca46c72..a5ba9273f3 100644 --- a/Examples/MagicWeather/MagicWeather.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/Examples/MagicWeather/MagicWeather.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -5,9 +5,9 @@ "package": "Purchases", "repositoryURL": "https://github.com/RevenueCat/purchases-ios.git", "state": { - "branch": null, - "revision": "9e31fb34880e77c598c2f1058b71b3b321f8ff4c", - "version": "3.12.0-beta.1" + "branch": "main", + "revision": "9581bcc9049efe52ae7e4d4e5924cb69169fd368", + "version": null } } ] diff --git a/Examples/MagicWeather/MagicWeather/Sources/Controllers/UserViewController.swift b/Examples/MagicWeather/MagicWeather/Sources/Controllers/UserViewController.swift index 886c8788d6..c7fa921b60 100644 --- a/Examples/MagicWeather/MagicWeather/Sources/Controllers/UserViewController.swift +++ b/Examples/MagicWeather/MagicWeather/Sources/Controllers/UserViewController.swift @@ -69,7 +69,7 @@ extension UserViewController { #warning("Public-facing usernames aren't optimal for user ID's - you should use something non-guessable, like a non-public database ID. For more information, visit https://docs.revenuecat.com/docs/user-ids.") /// - Call `identify` with the Purchases SDK with the unique user ID - Purchases.shared.identify(username) { (purchaserInfo, error) in + Purchases.shared.logIn(username) { (purchaserInfo, created, error) in if let error = error { self.present(UIAlertController.errorAlert(message: error.localizedDescription), animated: true, completion: nil) } @@ -92,7 +92,7 @@ extension UserViewController { Note: Each time you call `reset`, a new installation will be logged in the RevenueCat dashboard as that metric tracks unique user ID's that are in-use. Since this method generates a new anonymous ID, it counts as a new user ID in-use. */ - Purchases.shared.reset { (purchaserInfo, error) in + Purchases.shared.logOut { (purchaserInfo, error) in if let error = error { self.present(UIAlertController.errorAlert(message: error.localizedDescription), animated: true, completion: nil) } else { diff --git a/Examples/MagicWeatherSwiftUI/Magic Weather SwiftUI.xcodeproj/project.pbxproj b/Examples/MagicWeatherSwiftUI/Magic Weather SwiftUI.xcodeproj/project.pbxproj index d8faa3a8ae..6bcfe4d9f7 100644 --- a/Examples/MagicWeatherSwiftUI/Magic Weather SwiftUI.xcodeproj/project.pbxproj +++ b/Examples/MagicWeatherSwiftUI/Magic Weather SwiftUI.xcodeproj/project.pbxproj @@ -418,8 +418,8 @@ isa = XCRemoteSwiftPackageReference; repositoryURL = "https://github.com/RevenueCat/purchases-ios.git"; requirement = { - kind = upToNextMajorVersion; - minimumVersion = 3.12.0-SNAPSHOT; + branch = main; + kind = branch; }; }; /* End XCRemoteSwiftPackageReference section */ diff --git a/Examples/MagicWeatherSwiftUI/Magic Weather SwiftUI.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/Examples/MagicWeatherSwiftUI/Magic Weather SwiftUI.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index 280ca46c72..a5ba9273f3 100644 --- a/Examples/MagicWeatherSwiftUI/Magic Weather SwiftUI.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/Examples/MagicWeatherSwiftUI/Magic Weather SwiftUI.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -5,9 +5,9 @@ "package": "Purchases", "repositoryURL": "https://github.com/RevenueCat/purchases-ios.git", "state": { - "branch": null, - "revision": "9e31fb34880e77c598c2f1058b71b3b321f8ff4c", - "version": "3.12.0-beta.1" + "branch": "main", + "revision": "9581bcc9049efe52ae7e4d4e5924cb69169fd368", + "version": null } } ] diff --git a/IntegrationTests/CocoapodsIntegration/Gemfile.lock b/IntegrationTests/CocoapodsIntegration/Gemfile.lock index cb68edc870..7b21c162c9 100644 --- a/IntegrationTests/CocoapodsIntegration/Gemfile.lock +++ b/IntegrationTests/CocoapodsIntegration/Gemfile.lock @@ -2,7 +2,7 @@ GEM remote: https://rubygems.org/ specs: CFPropertyList (3.0.2) - addressable (2.7.0) + addressable (2.8.0) public_suffix (>= 2.0.2, < 5.0) atomos (0.1.3) aws-eventstream (1.1.0) diff --git a/IntegrationTests/PurchaseTester/Configuration.storekit b/IntegrationTests/PurchaseTester/Configuration.storekit index fa129be200..bf6bbecd61 100644 --- a/IntegrationTests/PurchaseTester/Configuration.storekit +++ b/IntegrationTests/PurchaseTester/Configuration.storekit @@ -21,7 +21,7 @@ } ], "settings" : { - + "_timeRate" : 6 }, "subscriptionGroups" : [ { diff --git a/IntegrationTests/SPMIntegration/fastlane/README.md b/IntegrationTests/SPMIntegration/fastlane/README.md new file mode 100644 index 0000000000..3c78c81142 --- /dev/null +++ b/IntegrationTests/SPMIntegration/fastlane/README.md @@ -0,0 +1,102 @@ +fastlane documentation +================ +# Installation + +Make sure you have the latest version of the Xcode command line tools installed: + +``` +xcode-select --install +``` + +Install _fastlane_ using +``` +[sudo] gem install fastlane -NV +``` +or alternatively using `brew install fastlane` + +# Available Actions +### update_swift_package_commit +``` +fastlane update_swift_package_commit +``` +Update swift package commit + +---- + +## iOS +### ios setup_dev +``` +fastlane ios setup_dev +``` +Setup development environment +### ios test +``` +fastlane ios test +``` +Runs all the tests +### ios bump +``` +fastlane ios bump +``` +Increment build number +### ios bump_and_update_changelog +``` +fastlane ios bump_and_update_changelog +``` +Increment build number and update changelog +### ios github_release +``` +fastlane ios github_release +``` +Make github release +### ios create_sandbox_account +``` +fastlane ios create_sandbox_account +``` +Create sandbox account +### ios deployment_checks +``` +fastlane ios deployment_checks +``` +Deployment checks +### ios carthage_archive +``` +fastlane ios carthage_archive +``` +Run the carthage archive steps to prepare for carthage distribution +### ios archive +``` +fastlane ios archive +``` +archive +### ios replace_api_key_integration_tests +``` +fastlane ios replace_api_key_integration_tests +``` +replace API KEY for integration tests +### ios deploy +``` +fastlane ios deploy +``` +Deploy +### ios prepare_next_version +``` +fastlane ios prepare_next_version +``` +Prepare next version +### ios export_xcframework +``` +fastlane ios export_xcframework +``` +Export XCFramework +### ios storekit_tests +``` +fastlane ios storekit_tests +``` +Run StoreKitTests + +---- + +This README.md is auto-generated and will be re-generated every time [_fastlane_](https://fastlane.tools) is run. +More information about fastlane can be found on [fastlane.tools](https://fastlane.tools). +The documentation of fastlane can be found on [docs.fastlane.tools](https://docs.fastlane.tools). diff --git a/Purchases.podspec b/Purchases.podspec index 3fef5440cc..5f907c950c 100644 --- a/Purchases.podspec +++ b/Purchases.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = "Purchases" - s.version = "3.12.0-SNAPSHOT" + s.version = "3.13.0-SNAPSHOT" s.summary = "Subscription and in-app-purchase backend service." s.description = <<-DESC @@ -22,7 +22,7 @@ Pod::Spec.new do |s| s.tvos.deployment_target = '9.0' s.pod_target_xcconfig = { 'DEFINES_MODULE' => 'YES' } - s.dependency 'PurchasesCoreSwift', '3.12.0-SNAPSHOT' + s.dependency 'PurchasesCoreSwift', '3.13.0-SNAPSHOT' s.source_files = ['Purchases/**/*.{h,m}'] diff --git a/Purchases.xcodeproj/project.pbxproj b/Purchases.xcodeproj/project.pbxproj index 26f66eaca1..53bccdf351 100644 --- a/Purchases.xcodeproj/project.pbxproj +++ b/Purchases.xcodeproj/project.pbxproj @@ -2134,6 +2134,7 @@ CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; CODE_SIGN_STYLE = Automatic; DEVELOPMENT_ASSET_PATHS = "\"StoreKitTestApp/Preview Content\""; + DEVELOPMENT_TEAM = ""; ENABLE_PREVIEWS = YES; INFOPLIST_FILE = StoreKitTestApp/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 14.1; @@ -2161,6 +2162,7 @@ CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; CODE_SIGN_STYLE = Automatic; DEVELOPMENT_ASSET_PATHS = "\"StoreKitTestApp/Preview Content\""; + DEVELOPMENT_TEAM = ""; ENABLE_PREVIEWS = YES; INFOPLIST_FILE = StoreKitTestApp/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 14.1; diff --git a/Purchases/Info.plist b/Purchases/Info.plist index b1f0d492aa..c9a5d84afa 100644 --- a/Purchases/Info.plist +++ b/Purchases/Info.plist @@ -17,7 +17,7 @@ CFBundlePackageType FMWK CFBundleShortVersionString - 3.12.0-SNAPSHOT + 3.13.0 CFBundleVersion $(CURRENT_PROJECT_VERSION) NSHumanReadableCopyright diff --git a/Purchases/Networking/RCHTTPClient.m b/Purchases/Networking/RCHTTPClient.m index 2606df637b..9983a59844 100644 --- a/Purchases/Networking/RCHTTPClient.m +++ b/Purchases/Networking/RCHTTPClient.m @@ -25,8 +25,6 @@ @interface RCHTTPClient () @implementation RCHTTPClient -typedef void (^RetryRequestBlock)(void); - - (instancetype)initWithSystemInfo:(RCSystemInfo *)systemInfo eTagManager:(RCETagManager *)eTagManager { if (self = [super init]) { diff --git a/Purchases/Public/RCPurchases.h b/Purchases/Public/RCPurchases.h index 6228874b30..191a9f2655 100644 --- a/Purchases/Public/RCPurchases.h +++ b/Purchases/Public/RCPurchases.h @@ -179,8 +179,10 @@ NS_SWIFT_NAME(Purchases) /** Set this to true if you are passing in an appUserID but it is anonymous, this is true by default if you didn't pass an appUserID If a user tries to purchase a product that is active on the current app store account, we will treat it as a restore and alias the new ID with the previous id. + See https://docs.revenuecat.com/docs/user-ids */ -@property (nonatomic) BOOL allowSharingAppStoreAccount; +@property (nonatomic) BOOL allowSharingAppStoreAccount + __attribute((deprecated("Configure behavior through the RevenueCat dashboard instead."))); /// Default to YES, set this to NO if you are finishing transactions with your own StoreKit queue listener @property (nonatomic) BOOL finishTransactions; @@ -205,20 +207,39 @@ NS_SWIFT_NAME(Purchases) @param completion An optional completion block called when the aliasing has been successful. This completion block will receive an error if there's been one. */ - (void)createAlias:(NSString *)alias completionBlock:(nullable RCReceivePurchaserInfoBlock)completion -NS_SWIFT_NAME(createAlias(_:_:)); +NS_SWIFT_NAME(createAlias(_:_:)) __attribute((deprecated("Use logIn instead."))); /** - This function will identify the current user with an appUserID. Typically this would be used after a logout to identify a new user without calling configure - @param appUserID The appUserID that should be linked to the currently user + This function will identify the current user with an appUserID. Typically this would be used after a logout to identify a new user without calling configure. + @param appUserID The appUserID that should be linked to the current user. */ - (void)identify:(NSString *)appUserID completionBlock:(nullable RCReceivePurchaserInfoBlock)completion -NS_SWIFT_NAME(identify(_:_:)); +NS_SWIFT_NAME(identify(_:_:)) __attribute((deprecated("Use logIn instead."))); /** * Resets the Purchases client clearing the saved appUserID. This will generate a random user id and save it in the cache. */ - (void)resetWithCompletionBlock:(nullable RCReceivePurchaserInfoBlock)completion -NS_SWIFT_NAME(reset(_:)); +NS_SWIFT_NAME(reset(_:)) __attribute((deprecated("Use logOut instead."))); + +/** + This function will logIn the current user with an appUserID. + @param appUserID The appUserID that should be linked to the current user. + The callback will be called with the latest PurchaserInfo for the user, as well as a boolean indicating whether the user was created for the first + time in the RevenueCat backend. + See https://docs.revenuecat.com/docs/user-ids + */ +- (void) logIn:(NSString *)appUserID +completionBlock:(void (^)(RCPurchaserInfo * _Nullable purchaserInfo, BOOL created, NSError * _Nullable error))completion +NS_SWIFT_NAME(logIn(_:_:)); + +/** + Logs out the Purchases client clearing the saved appUserID. This will generate a random user id and save it in the cache. + If this method is called and the current user is anonymous, it will return an error. + See https://docs.revenuecat.com/docs/user-ids + */ +- (void)logOutWithCompletionBlock:(nullable RCReceivePurchaserInfoBlock)completion +NS_SWIFT_NAME(logOut(_:)); #pragma mark Attribution diff --git a/Purchases/Public/RCPurchases.m b/Purchases/Public/RCPurchases.m index 1c88fdd931..48b3e338e9 100644 --- a/Purchases/Public/RCPurchases.m +++ b/Purchases/Public/RCPurchases.m @@ -81,7 +81,8 @@ + (BOOL)automaticAppleSearchAdsAttributionCollection { } + (void)setDebugLogsEnabled:(BOOL)enabled { - [self setLogLevel:RCLogLevelDebug]; + RCLogLevel level = enabled ? RCLogLevelDebug : RCLogLevelInfo; + [self setLogLevel:level]; } + (BOOL)debugLogsEnabled { diff --git a/PurchasesCoreSwift.podspec b/PurchasesCoreSwift.podspec index 513f668d2d..a7a542a658 100644 --- a/PurchasesCoreSwift.podspec +++ b/PurchasesCoreSwift.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = "PurchasesCoreSwift" - s.version = "3.12.0-SNAPSHOT" + s.version = "3.13.0-SNAPSHOT" s.summary = "Swift portion of RevenueCat's Subscription and in-app-purchase backend service." s.description = <<-DESC diff --git a/PurchasesCoreSwift/Info.plist b/PurchasesCoreSwift/Info.plist index 39cdcedcff..40796ed49b 100644 --- a/PurchasesCoreSwift/Info.plist +++ b/PurchasesCoreSwift/Info.plist @@ -15,7 +15,7 @@ CFBundlePackageType $(PRODUCT_BUNDLE_PACKAGE_TYPE) CFBundleShortVersionString - 3.12.0-SNAPSHOT + 3.13.0 CFBundleVersion $(CURRENT_PROJECT_VERSION) diff --git a/PurchasesCoreSwift/Networking/ETagManager.swift b/PurchasesCoreSwift/Networking/ETagManager.swift index 4ba05ed77b..4bd75ded9b 100644 --- a/PurchasesCoreSwift/Networking/ETagManager.swift +++ b/PurchasesCoreSwift/Networking/ETagManager.swift @@ -37,10 +37,10 @@ import Foundation guard error == nil else { return resultFromBackend } let headersInResponse = response.allHeaderFields - let eTagInResponse: String? = headersInResponse[ETagManager.eTagHeaderName] as? String ?? + let maybeETagInResponse: String? = headersInResponse[ETagManager.eTagHeaderName] as? String ?? headersInResponse[ETagManager.eTagHeaderName.lowercased()] as? String - guard let eTagInResponse = eTagInResponse else { return resultFromBackend } + guard let eTagInResponse = maybeETagInResponse else { return resultFromBackend } if shouldUseCachedVersion(responseCode: statusCode) { if let storedResponse = storedHTTPResponse(for: request) { return storedResponse diff --git a/PurchasesCoreSwiftTests/Info.plist b/PurchasesCoreSwiftTests/Info.plist index a35e47ddd7..0fb2a3db97 100644 --- a/PurchasesCoreSwiftTests/Info.plist +++ b/PurchasesCoreSwiftTests/Info.plist @@ -15,7 +15,7 @@ CFBundlePackageType $(PRODUCT_BUNDLE_PACKAGE_TYPE) CFBundleShortVersionString - 3.12.0-SNAPSHOT + 3.13.0 CFBundleVersion 1 diff --git a/PurchasesTests/Info.plist b/PurchasesTests/Info.plist index db4a0c450f..84dcc1d73a 100644 --- a/PurchasesTests/Info.plist +++ b/PurchasesTests/Info.plist @@ -17,7 +17,7 @@ CFBundlePackageType BNDL CFBundleShortVersionString - 3.12.0-SNAPSHOT + 3.13.0 CFBundleVersion 1 diff --git a/PurchasesTests/Networking/HTTPClientTests.swift b/PurchasesTests/Networking/HTTPClientTests.swift index a6600065ec..6f8a2de944 100644 --- a/PurchasesTests/Networking/HTTPClientTests.swift +++ b/PurchasesTests/Networking/HTTPClientTests.swift @@ -44,7 +44,7 @@ class HTTPClientTests: XCTestCase { expectToThrowException(.parameterAssert) { self.client.performRequest("GE", serially: true, path: "/", body: Dictionary.init(), headers: nil, completionHandler: nil) - } + } } func testUsesTheCorrectHost() { @@ -213,9 +213,9 @@ class HTTPClientTests: XCTestCase { self.client.performRequest("GET", serially: true, path: path, body: nil, headers: nil) { (status, data, responseError) in if let responseNSError = responseError as NSError? { successFailed = (status >= 500 - && data == nil - && error.domain == responseNSError.domain - && error.code == responseNSError.code) + && data == nil + && error.domain == responseNSError.domain + && error.code == responseNSError.code) } else { successFailed = false } @@ -387,7 +387,7 @@ class HTTPClientTests: XCTestCase { let client = RCHTTPClient(systemInfo: systemInfo, eTagManager: eTagManager) client.performRequest("POST", serially: true, path: path, body: Dictionary.init(), - headers: ["test_header": "value"], completionHandler:nil) + headers: ["test_header": "value"], completionHandler:nil) expect(headerPresent).toEventually(equal(true)) } @@ -406,7 +406,7 @@ class HTTPClientTests: XCTestCase { let client = RCHTTPClient(systemInfo: systemInfo, eTagManager: eTagManager) client.performRequest("POST", serially: true, path: path, body: Dictionary.init(), - headers: ["test_header": "value"], completionHandler:nil) + headers: ["test_header": "value"], completionHandler:nil) expect(headerPresent).toEventually(equal(true)) } @@ -423,7 +423,7 @@ class HTTPClientTests: XCTestCase { let client = RCHTTPClient(systemInfo: systemInfo, eTagManager: eTagManager) client.performRequest("POST", serially: true, path: path, body: Dictionary.init(), - headers: ["test_header": "value"], completionHandler:nil) + headers: ["test_header": "value"], completionHandler:nil) expect(headerPresent).toEventually(equal(true)) } @@ -440,7 +440,7 @@ class HTTPClientTests: XCTestCase { let client = RCHTTPClient(systemInfo: systemInfo, eTagManager: eTagManager) client.performRequest("POST", serially: true, path: path, body: Dictionary.init(), - headers: ["test_header": "value"], completionHandler:nil) + headers: ["test_header": "value"], completionHandler:nil) expect(headerPresent).toEventually(equal(true)) } diff --git a/PurchasesTests/Purchasing/PurchasesTests.swift b/PurchasesTests/Purchasing/PurchasesTests.swift index abcb66e986..6095836b7a 100644 --- a/PurchasesTests/Purchasing/PurchasesTests.swift +++ b/PurchasesTests/Purchasing/PurchasesTests.swift @@ -2583,6 +2583,16 @@ class PurchasesTests: XCTestCase { } } + func testSetDebugLogsEnabledSetsTheCorrectValue() { + Logger.logLevel = .warn + + Purchases.debugLogsEnabled = true + expect(Logger.logLevel) == .debug + + Purchases.debugLogsEnabled = false + expect(Logger.logLevel) == .info + } + private func verifyUpdatedCaches(newAppUserID: String) { let expectedCallCount = 2 diff --git a/README.md b/README.md index c402ed43c1..c928933f73 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,4 @@ -

- RevenueCat -

-

😻 In-app Subscriptions Made Easy 😻

+

😻 In-App Subscriptions Made Easy 😻

[![License](https://img.shields.io/cocoapods/l/Purchases.svg?style=flat)](http://cocoapods.org/pods/Purchases) [![Version](https://img.shields.io/cocoapods/v/Purchases.svg?style=flat)](https://cocoapods.org/pods/Purchases) diff --git a/StoreKitTestApp/Info.plist b/StoreKitTestApp/Info.plist index efc211a0c1..45f212287d 100644 --- a/StoreKitTestApp/Info.plist +++ b/StoreKitTestApp/Info.plist @@ -15,7 +15,7 @@ CFBundlePackageType $(PRODUCT_BUNDLE_PACKAGE_TYPE) CFBundleShortVersionString - 1.0 + 3.13.0 CFBundleVersion 1 LSRequiresIPhoneOS diff --git a/StoreKitTests/Info.plist b/StoreKitTests/Info.plist index 64d65ca495..0fb2a3db97 100644 --- a/StoreKitTests/Info.plist +++ b/StoreKitTests/Info.plist @@ -15,7 +15,7 @@ CFBundlePackageType $(PRODUCT_BUNDLE_PACKAGE_TYPE) CFBundleShortVersionString - 1.0 + 3.13.0 CFBundleVersion 1 diff --git a/StoreKitTests/StoreKitTests.swift b/StoreKitTests/StoreKitTests.swift index 9e72dea773..aa8f54235a 100644 --- a/StoreKitTests/StoreKitTests.swift +++ b/StoreKitTests/StoreKitTests.swift @@ -11,15 +11,32 @@ import Purchases import Nimble import StoreKitTest +class TestPurchaseDelegate: NSObject, PurchasesDelegate { + var purchaserInfo: Purchases.PurchaserInfo? + var purchaserInfoUpdateCount = 0 + + func purchases(_ purchases: Purchases, didReceiveUpdated purchaserInfo: Purchases.PurchaserInfo) { + self.purchaserInfo = purchaserInfo + purchaserInfoUpdateCount += 1 + } + + func purchases(_ purchases: Purchases, + shouldPurchasePromoProduct product: SKProduct, + defermentBlock makeDeferredPurchase: @escaping RCDeferredPromotionalPurchaseBlock) { + } +} + class StoreKitTests: XCTestCase { var testSession: SKTestSession! var userDefaults: UserDefaults! - + var purchasesDelegate: TestPurchaseDelegate! + override func setUpWithError() throws { testSession = try SKTestSession(configurationFileNamed: Constants.storeKitConfigFileName) testSession.disableDialogs = true - + testSession.clearTransactions() + userDefaults = UserDefaults(suiteName: Constants.userDefaultsSuiteName) userDefaults?.removePersistentDomain(forName: Constants.userDefaultsSuiteName) if !Constants.proxyURL.isEmpty { @@ -27,16 +44,8 @@ class StoreKitTests: XCTestCase { } } - override func tearDownWithError() throws { - testSession.clearTransactions() - } - - func testExample() throws { - Purchases.configure(withAPIKey: Constants.apiKey, - appUserID: nil, - observerMode: false, - userDefaults: userDefaults) - Purchases.debugLogsEnabled = true + func testCanGetOfferings() throws { + configurePurchases() var completionCalled = false var receivedError: Error? = nil var receivedOfferings: Offerings? = nil @@ -46,9 +55,225 @@ class StoreKitTests: XCTestCase { receivedOfferings = offerings } expect(completionCalled).toEventually(beTrue(), timeout: .seconds(10)) - + expect(receivedError).to(beNil()) expect(receivedOfferings).toNot(beNil()) expect(receivedOfferings!.all).toNot(beEmpty()) } + + func testCanMakePurchase() throws { + configurePurchases() + purchaseMonthlyOffering() + + waitUntilEntitlementsGoThrough() + let entitlements = purchasesDelegate.purchaserInfo?.entitlements + expect(entitlements?["premium"]?.isActive) == true + } + + func testPurchaseMadeBeforeLogInIsRetainedAfter() { + configurePurchases() + + var completionCalled = false + purchaseMonthlyOffering { [self] purchaserInfo, error in + expect(purchaserInfo?.entitlements.all.count) == 1 + let entitlements = self.purchasesDelegate.purchaserInfo?.entitlements + expect(entitlements?["premium"]?.isActive) == true + + let anonUserID = Purchases.shared.appUserID + let identifiedUserID = "\(#function)_\(anonUserID)_".replacingOccurrences(of: "RCAnonymous", with: "") + + Purchases.shared.logIn(identifiedUserID) { identifiedPurchaserInfo, created, error in + expect(error).to(beNil()) + + expect(created).to(beTrue()) + expect(identifiedPurchaserInfo?.entitlements["premium"]?.isActive) == true + completionCalled = true + } + } + expect(completionCalled).toEventually(beTrue(), timeout: .seconds(10)) + } + + func testPurchaseMadeBeforeLogInWithExistingUserIsNotRetainedUnlessRestoreCalled() { + configurePurchases() + var completionCalled = false + let existingUserID = "\(#function)\(UUID().uuidString)" + expect(self.purchasesDelegate.purchaserInfoUpdateCount).toEventually(equal(1), timeout: .seconds(10)) + + // log in to create the user, then log out + Purchases.shared.logIn(existingUserID) { logInPurchaserInfo, created, logInError in + Purchases.shared.logOut() { loggedOutPurchaserInfo, logOutError in + completionCalled = true + } + } + + expect(completionCalled).toEventually(beTrue(), timeout: .seconds(10)) + + // purchase as anonymous user, then log in + purchaseMonthlyOffering() + waitUntilEntitlementsGoThrough() + + completionCalled = false + + Purchases.shared.logIn(existingUserID) { purchaserInfo, created, logInError in + completionCalled = true + self.assertNoPurchases(purchaserInfo) + expect(created).to(beFalse()) + expect(logInError).to(beNil()) + } + + expect(completionCalled).toEventually(beTrue(), timeout: .seconds(10)) + + Purchases.shared.restoreTransactions() + + waitUntilEntitlementsGoThrough() + } + + func testPurchaseAsIdentifiedThenLogOutThenRestoreGrantsEntitlements() { + configurePurchases() + var completionCalled = false + let existingUserID = UUID().uuidString + expect(self.purchasesDelegate.purchaserInfoUpdateCount).toEventually(equal(1), timeout: .seconds(10)) + + Purchases.shared.logIn(existingUserID) { logInPurchaserInfo, created, logInError in + self.purchaseMonthlyOffering() + completionCalled = true + } + + expect(completionCalled).toEventually(beTrue(), timeout: .seconds(10)) + + waitUntilEntitlementsGoThrough() + + completionCalled = false + + Purchases.shared.logOut { purchaserInfo, error in + self.assertNoPurchases(purchaserInfo) + expect(error).to(beNil()) + completionCalled = true + } + + expect(completionCalled).toEventually(beTrue(), timeout: .seconds(10)) + + Purchases.shared.restoreTransactions() + + waitUntilEntitlementsGoThrough() + } + + func testLogInReturnsCreatedTrueWhenNewAndFalseWhenExisting() { + configurePurchases() + + let anonUserID = Purchases.shared.appUserID + let identifiedUserID = "\(#function)_\(anonUserID)".replacingOccurrences(of: "RCAnonymous", with: "") + + var completionCalled = false + Purchases.shared.logIn(identifiedUserID) { identifiedPurchaserInfo, created, error in + expect(error).to(beNil()) + expect(created).to(beTrue()) + Purchases.shared.logOut { loggedOutPurchaserInfo, logOutError in + Purchases.shared.logIn(identifiedUserID) { identifiedPurchaserInfo, created, error in + expect(error).to(beNil()) + expect(created).to(beFalse()) + completionCalled = true + } + } + } + + expect(completionCalled).toEventually(beTrue(), timeout: .seconds(10)) + } + + func testLogInThenLogInAsAnotherUserWontTransferPurchases() { + configurePurchases() + + let userID1 = UUID().uuidString + let userID2 = UUID().uuidString + + Purchases.shared.logIn(userID1) { identifiedPurchaserInfo, created, error in + self.purchaseMonthlyOffering() + } + + waitUntilEntitlementsGoThrough() + + testSession.clearTransactions() + + Purchases.shared.logIn(userID2) { identifiedPurchaserInfo, created, error in + self.assertNoPurchases(identifiedPurchaserInfo) + expect(error).to(beNil()) + } + + expect(self.purchasesDelegate.purchaserInfo?.originalAppUserId) + .toEventually(equal(userID2), timeout: .seconds(10)) + assertNoPurchases(purchasesDelegate.purchaserInfo) + } + + func testLogOutRemovesEntitlements() { + configurePurchases() + + let anonUserID = Purchases.shared.appUserID + let identifiedUserID = "identified_\(anonUserID)".replacingOccurrences(of: "RCAnonymous", with: "") + + Purchases.shared.logIn(identifiedUserID) { identifiedPurchaserInfo, created, error in + expect(error).to(beNil()) + + expect(created).to(beTrue()) + print("identifiedPurchaserInfo: \(String(describing: identifiedPurchaserInfo))") + + self.purchaseMonthlyOffering() + } + + waitUntilEntitlementsGoThrough() + + var completionCalled = false + Purchases.shared.logOut { loggedOutPurchaserInfo, logOutError in + expect(logOutError).to(beNil()) + self.assertNoPurchases(loggedOutPurchaserInfo) + completionCalled = true + } + + expect(completionCalled).toEventually(beTrue(), timeout: .seconds(10)) + } +} + +private extension StoreKitTests { + + func purchaseMonthlyOffering(completion: ((Purchases.PurchaserInfo?, Error?) -> Void)? = nil) { + Purchases.shared.offerings { offerings, error in + expect(error).to(beNil()) + + let offering = offerings?.current + expect(offering).toNot(beNil()) + + let monthlyPackage = offering?.monthly + expect(monthlyPackage).toNot(beNil()) + + Purchases.shared.purchaseProduct(monthlyPackage!.product) { transaction, + purchaserInfo, + purchaseError, + userCancelled in + expect(purchaseError).to(beNil()) + expect(purchaserInfo).toNot(beNil()) + } + + DispatchQueue.main.asyncAfter(deadline: .now() + .seconds(2)) { + Purchases.shared.syncPurchases(completion) + } + } + } + + func configurePurchases() { + purchasesDelegate = TestPurchaseDelegate() + Purchases.configure(withAPIKey: Constants.apiKey, + appUserID: nil, + observerMode: false, + userDefaults: userDefaults) + Purchases.logLevel = .debug + Purchases.shared.delegate = purchasesDelegate + } + + func waitUntilEntitlementsGoThrough() { + expect(self.purchasesDelegate.purchaserInfo?.entitlements.all.count) + .toEventually(equal(1), timeout: .seconds(10)) + } + + func assertNoPurchases(_ purchaserInfo: Purchases.PurchaserInfo?) { + expect(purchaserInfo?.entitlements.all.count) == 0 + } } diff --git a/fastlane/Fastfile b/fastlane/Fastfile index 3d8eea4c98..841c3f3233 100644 --- a/fastlane/Fastfile +++ b/fastlane/Fastfile @@ -146,8 +146,8 @@ platform :ios do replace_in("REVENUECAT_API_KEY", ENV["REVENUECAT_API_KEY"], '../StoreKitTests/Constants.swift') replace_in("REVENUECAT_API_KEY", ENV["REVENUECAT_API_KEY"], '../IntegrationTests/CommonFiles/RCIntegrationRunner.m') - replace_in("REVENUECAT_PROXY_URL", ENV["REVENUECAT_PROXY_URL"].to_s, '../StoreKitTests/Constants.swift') - replace_in("REVENUECAT_PROXY_URL", ENV["REVENUECAT_PROXY_URL"].to_s, '../IntegrationTests/CommonFiles/RCIntegrationRunner.m') + replace_in("REVENUECAT_PROXY_URL", ENV["REVENUECAT_PROXY_URL"].to_s, '../StoreKitTests/Constants.swift', allow_empty: true) + replace_in("REVENUECAT_PROXY_URL", ENV["REVENUECAT_PROXY_URL"].to_s, '../IntegrationTests/CommonFiles/RCIntegrationRunner.m', allow_empty: true) end desc "Deploy" @@ -161,8 +161,8 @@ platform :ios do desc "Prepare next version" lane :prepare_next_version do |options| - current_version_number = current_version_number - major, minor, _ = current_version_number.split('.') + old_version_number = current_version_number + major, minor, _ = old_version_number.split('.') next_version = "#{major}.#{minor.to_i + 1}.0" next_version_snapshot = "#{next_version}-SNAPSHOT" @@ -218,6 +218,27 @@ platform :ios do scan(scheme: "StoreKitTests", derived_data_path: "scan_derived_data") end + desc "Update swift package commit" + lane :update_swift_package_commit do + project_file_locations = [ + '../IntegrationTests/SPMIntegration/SPMIntegration.xcodeproj/project.pbxproj', + '../Examples/MagicWeather/MagicWeather.xcodeproj/project.pbxproj', + '../Examples/MagicWeatherSwiftUI/Magic Weather SwiftUI.xcodeproj/project.pbxproj', + ] + + old_kind_line = "kind = branch;" + new_kind_line = "kind = revision;" + + commit_hash = last_git_commit[:commit_hash] + old_branch_line = "branch = main;" + new_revision_line = "revision = #{commit_hash};" + + project_file_locations.each { |project_file_location| + replace_in(old_kind_line, new_kind_line, project_file_location) + replace_in(old_branch_line, new_revision_line, project_file_location) + } + end + end def push_pods @@ -239,8 +260,13 @@ end def carthage_archive Dir.chdir("..") do - sh("carthage", "build", "--no-skip-current", "--use-xcframeworks") - sh("carthage", "archive", "Purchases") + # As of Carthage 0.38.0, we still can't archive xcframeworks directly. + # there are also some issues which prevent us from archiving frameworks directly, since + # carthage can't deal with architectures for simulators and for Apple Silicon correctly. + # We use this script as a workaround until this is fixed. + # https://github.com/Carthage/Carthage/releases/0.38.0 + sh("./scripts/carthage.sh", "build", "--no-skip-current") + sh("./scripts/carthage.sh", "archive", "Purchases") end end @@ -272,8 +298,8 @@ def attach_changelog_to_master(version_number) } end -def replace_in(previous_text, new_text, path) - if new_text.to_s.strip.empty? +def replace_in(previous_text, new_text, path, allow_empty=false) + if new_text.to_s.strip.empty? and not allow_empty fail "Missing `new_text` in call to `replace_in`, looking for replacement for #{previous_text} 😵." end sed_regex = 's|' + previous_text + '|' + new_text + '|' diff --git a/fastlane/README.md b/fastlane/README.md index 2cd83f90b4..0b66a0a587 100644 --- a/fastlane/README.md +++ b/fastlane/README.md @@ -91,9 +91,14 @@ Export XCFramework fastlane ios storekit_tests ``` Run StoreKitTests +### ios update_swift_package_commit +``` +fastlane ios update_swift_package_commit +``` +Update swift package commit ---- -This README.md is auto-generated and will be re-generated every time [fastlane](https://fastlane.tools) is run. +This README.md is auto-generated and will be re-generated every time [_fastlane_](https://fastlane.tools) is run. More information about fastlane can be found on [fastlane.tools](https://fastlane.tools). The documentation of fastlane can be found on [docs.fastlane.tools](https://docs.fastlane.tools). diff --git a/scripts/carthage.sh b/scripts/carthage.sh new file mode 100755 index 0000000000..5aee833963 --- /dev/null +++ b/scripts/carthage.sh @@ -0,0 +1,21 @@ +# This script is needed to use Carthage in Xcode 12, since it currently has a compatibility issue +# due to the support for Apple Silicon, which in turn creates a duplicated architecture. +# https://github.com/Carthage/Carthage/blob/master/Documentation/Xcode12Workaround.md +# https://github.com/Carthage/Carthage/issues/3019 + +set -euo pipefail + +xcconfig=$(mktemp /tmp/static.xcconfig.XXXXXX) +trap 'rm -f "$xcconfig"' INT TERM HUP EXIT + +# For Xcode 12 make sure EXCLUDED_ARCHS is set to arm architectures otherwise +# the build will fail on lipo due to duplicate architectures. + +CURRENT_XCODE_VERSION=$(xcodebuild -version | grep "Build version" | cut -d' ' -f3) +echo "EXCLUDED_ARCHS__EFFECTIVE_PLATFORM_SUFFIX_simulator__NATIVE_ARCH_64_BIT_x86_64__XCODE_1200__BUILD_$CURRENT_XCODE_VERSION = arm64 arm64e armv7 armv7s armv6 armv8" >> $xcconfig + +echo 'EXCLUDED_ARCHS__EFFECTIVE_PLATFORM_SUFFIX_simulator__NATIVE_ARCH_64_BIT_x86_64__XCODE_1200 = $(EXCLUDED_ARCHS__EFFECTIVE_PLATFORM_SUFFIX_simulator__NATIVE_ARCH_64_BIT_x86_64__XCODE_1200__BUILD_$(XCODE_PRODUCT_BUILD_VERSION))' >> $xcconfig +echo 'EXCLUDED_ARCHS = $(inherited) $(EXCLUDED_ARCHS__EFFECTIVE_PLATFORM_SUFFIX_$(EFFECTIVE_PLATFORM_SUFFIX)__NATIVE_ARCH_64_BIT_$(NATIVE_ARCH_64_BIT)__XCODE_$(XCODE_VERSION_MAJOR))' >> $xcconfig + +export XCODE_XCCONFIG_FILE="$xcconfig" +carthage "$@" \ No newline at end of file