diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index fcf0dfbb..8196efa5 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -8,7 +8,7 @@ on: branches: [ develop ] env: - DEVELOPER_DIR: /Applications/Xcode_13.2.1.app/Contents/Developer + DEVELOPER_DIR: /Applications/Xcode_15.4.app/Contents/Developer jobs: tests: @@ -16,20 +16,21 @@ jobs: runs-on: ${{ matrix.os }} strategy: matrix: - os: [macos-12] - swift: ["5.5"] + os: [macos-14] + swift: ["5.10"] steps: - uses: actions/checkout@v2 - - uses: fwal/setup-swift@v1 + + - uses: swift-actions/setup-swift@d4537ff835c9778c934e48f78639e270edd5839e # v2.2.0 with: swift-version: ${{ matrix.swift }} - - name: Make install + - name: Install variants run: make install - - - name: Tests - run: make test + + - name: Run CI validation + run: make ci-validation # # CODECOV temporarily disabled due to issues running 'bundle install' diff --git a/.github/workflows/danger-swift.yml b/.github/workflows/danger-swift.yml index 748b49c3..381c9014 100644 --- a/.github/workflows/danger-swift.yml +++ b/.github/workflows/danger-swift.yml @@ -8,9 +8,10 @@ jobs: danger-scan: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v1 + - uses: actions/checkout@v3 - name: Danger - uses: danger/swift@3.6.1 + uses: danger/swift@bb4faf2c5613960b3de954c631cedd4dc63c3fef # v3.21.1 + with: + args: --failOnErrors --no-publish-check env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - DANGER_GITHUB_API_TOKEN: ${{ secrets.DANGER_GITHUB_API_TOKEN }} + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} \ No newline at end of file diff --git a/.github/workflows/swiftlint.yml b/.github/workflows/swiftlint.yml index cdfc7871..2508cf5f 100644 --- a/.github/workflows/swiftlint.yml +++ b/.github/workflows/swiftlint.yml @@ -3,11 +3,20 @@ name: SwiftLint on: pull_request: types: [ opened, synchronize ] + paths: + - '.github/workflows/swiftlint.yml' + - '.swiftlint.yml' + - '**/*.swift' jobs: SwiftLint: - runs-on: macos-latest + runs-on: ubuntu-latest + permissions: + contents: read + pull-requests: write steps: - - uses: actions/checkout@v1 - - name: Run SwiftLint - run: swiftlint lint --strict --reporter github-actions-logging \ No newline at end of file + - uses: actions/checkout@v3 + - name: GitHub Action for SwiftLint + uses: stanfordbdhg/action-swiftlint@f6ee119765c4b81b667fa84b1e9ee77ca864f622 # v4.1.1 + with: + args: --strict --reporter github-actions-logging \ No newline at end of file diff --git a/.ruby-version b/.ruby-version index 49cdd668..bea438e9 100644 --- a/.ruby-version +++ b/.ruby-version @@ -1 +1 @@ -2.7.6 +3.3.1 diff --git a/Gemfile b/Gemfile index 727bc062..23763df4 100644 --- a/Gemfile +++ b/Gemfile @@ -1,4 +1,5 @@ source 'https://rubygems.org' +gem "fastlane" gem "slather" -gem "nokogiri", ">= 1.13.2" +gem "nokogiri" diff --git a/Gemfile.lock b/Gemfile.lock index ab351f32..92daa4ca 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -1,53 +1,269 @@ GEM remote: https://rubygems.org/ specs: - CFPropertyList (3.0.5) + CFPropertyList (3.0.7) + base64 + nkf rexml - activesupport (7.0.4.2) + activesupport (7.1.5.1) + base64 + benchmark (>= 0.3) + bigdecimal concurrent-ruby (~> 1.0, >= 1.0.2) + connection_pool (>= 2.2.5) + drb i18n (>= 1.6, < 2) + logger (>= 1.4.2) minitest (>= 5.1) + mutex_m + securerandom (>= 0.3) tzinfo (~> 2.0) + addressable (2.8.7) + public_suffix (>= 2.0.2, < 7.0) + artifactory (3.0.17) atomos (0.1.3) + aws-eventstream (1.3.0) + aws-partitions (1.1039.0) + aws-sdk-core (3.216.0) + aws-eventstream (~> 1, >= 1.3.0) + aws-partitions (~> 1, >= 1.992.0) + aws-sigv4 (~> 1.9) + jmespath (~> 1, >= 1.6.1) + aws-sdk-kms (1.97.0) + aws-sdk-core (~> 3, >= 3.216.0) + aws-sigv4 (~> 1.5) + aws-sdk-s3 (1.178.0) + aws-sdk-core (~> 3, >= 3.216.0) + aws-sdk-kms (~> 1) + aws-sigv4 (~> 1.5) + aws-sigv4 (1.11.0) + aws-eventstream (~> 1, >= 1.0.2) + babosa (1.0.4) + base64 (0.2.0) + benchmark (0.4.0) + bigdecimal (3.1.9) claide (1.1.0) clamp (1.3.2) + colored (1.2) colored2 (3.1.2) - concurrent-ruby (1.2.0) - i18n (1.12.0) + commander (4.6.0) + highline (~> 2.0.0) + concurrent-ruby (1.3.5) + connection_pool (2.5.0) + declarative (0.0.20) + digest-crc (0.6.5) + rake (>= 12.0.0, < 14.0.0) + domain_name (0.6.20240107) + dotenv (2.8.1) + drb (2.2.1) + emoji_regex (3.2.3) + excon (0.112.0) + faraday (1.10.4) + faraday-em_http (~> 1.0) + faraday-em_synchrony (~> 1.0) + faraday-excon (~> 1.1) + faraday-httpclient (~> 1.0) + faraday-multipart (~> 1.0) + faraday-net_http (~> 1.0) + faraday-net_http_persistent (~> 1.0) + faraday-patron (~> 1.0) + faraday-rack (~> 1.0) + faraday-retry (~> 1.0) + ruby2_keywords (>= 0.0.4) + faraday-cookie_jar (0.0.7) + faraday (>= 0.8.0) + http-cookie (~> 1.0.0) + faraday-em_http (1.0.0) + faraday-em_synchrony (1.0.0) + faraday-excon (1.1.0) + faraday-httpclient (1.0.1) + faraday-multipart (1.1.0) + multipart-post (~> 2.0) + faraday-net_http (1.0.2) + faraday-net_http_persistent (1.2.0) + faraday-patron (1.0.0) + faraday-rack (1.0.0) + faraday-retry (1.0.3) + faraday_middleware (1.2.1) + faraday (~> 1.0) + fastimage (2.4.0) + fastlane (2.226.0) + CFPropertyList (>= 2.3, < 4.0.0) + addressable (>= 2.8, < 3.0.0) + artifactory (~> 3.0) + aws-sdk-s3 (~> 1.0) + babosa (>= 1.0.3, < 2.0.0) + bundler (>= 1.12.0, < 3.0.0) + colored (~> 1.2) + commander (~> 4.6) + dotenv (>= 2.1.1, < 3.0.0) + emoji_regex (>= 0.1, < 4.0) + excon (>= 0.71.0, < 1.0.0) + faraday (~> 1.0) + faraday-cookie_jar (~> 0.0.6) + faraday_middleware (~> 1.0) + fastimage (>= 2.1.0, < 3.0.0) + fastlane-sirp (>= 1.0.0) + gh_inspector (>= 1.1.2, < 2.0.0) + google-apis-androidpublisher_v3 (~> 0.3) + google-apis-playcustomapp_v1 (~> 0.1) + google-cloud-env (>= 1.6.0, < 2.0.0) + google-cloud-storage (~> 1.31) + highline (~> 2.0) + http-cookie (~> 1.0.5) + json (< 3.0.0) + jwt (>= 2.1.0, < 3) + mini_magick (>= 4.9.4, < 5.0.0) + multipart-post (>= 2.0.0, < 3.0.0) + naturally (~> 2.2) + optparse (>= 0.1.1, < 1.0.0) + plist (>= 3.1.0, < 4.0.0) + rubyzip (>= 2.0.0, < 3.0.0) + security (= 0.1.5) + simctl (~> 1.6.3) + terminal-notifier (>= 2.0.0, < 3.0.0) + terminal-table (~> 3) + tty-screen (>= 0.6.3, < 1.0.0) + tty-spinner (>= 0.8.0, < 1.0.0) + word_wrap (~> 1.0.0) + xcodeproj (>= 1.13.0, < 2.0.0) + xcpretty (~> 0.4.0) + xcpretty-travis-formatter (>= 0.0.3, < 2.0.0) + fastlane-sirp (1.0.0) + sysrandom (~> 1.0) + gh_inspector (1.1.3) + google-apis-androidpublisher_v3 (0.54.0) + google-apis-core (>= 0.11.0, < 2.a) + google-apis-core (0.11.3) + addressable (~> 2.5, >= 2.5.1) + googleauth (>= 0.16.2, < 2.a) + httpclient (>= 2.8.1, < 3.a) + mini_mime (~> 1.0) + representable (~> 3.0) + retriable (>= 2.0, < 4.a) + rexml + google-apis-iamcredentials_v1 (0.17.0) + google-apis-core (>= 0.11.0, < 2.a) + google-apis-playcustomapp_v1 (0.13.0) + google-apis-core (>= 0.11.0, < 2.a) + google-apis-storage_v1 (0.31.0) + google-apis-core (>= 0.11.0, < 2.a) + google-cloud-core (1.7.1) + google-cloud-env (>= 1.0, < 3.a) + google-cloud-errors (~> 1.0) + google-cloud-env (1.6.0) + faraday (>= 0.17.3, < 3.0) + google-cloud-errors (1.4.0) + google-cloud-storage (1.47.0) + addressable (~> 2.8) + digest-crc (~> 0.4) + google-apis-iamcredentials_v1 (~> 0.1) + google-apis-storage_v1 (~> 0.31.0) + google-cloud-core (~> 1.6) + googleauth (>= 0.16.2, < 2.a) + mini_mime (~> 1.0) + googleauth (1.8.1) + faraday (>= 0.17.3, < 3.a) + jwt (>= 1.4, < 3.0) + multi_json (~> 1.11) + os (>= 0.9, < 2.0) + signet (>= 0.16, < 2.a) + highline (2.0.3) + http-cookie (1.0.8) + domain_name (~> 0.5) + httpclient (2.8.3) + i18n (1.14.6) concurrent-ruby (~> 1.0) - minitest (5.17.0) - nanaimo (0.3.0) - nokogiri (1.13.8-arm64-darwin) + jmespath (1.6.2) + json (2.9.1) + jwt (2.10.1) + base64 + logger (1.6.5) + mini_magick (4.13.2) + mini_mime (1.1.5) + mini_portile2 (2.8.8) + minitest (5.25.4) + multi_json (1.15.0) + multipart-post (2.4.1) + mutex_m (0.3.0) + nanaimo (0.4.0) + naturally (2.2.1) + nkf (0.2.0) + nokogiri (1.18.2) + mini_portile2 (~> 2.8.2) + racc (~> 1.4) + nokogiri (1.18.2-arm64-darwin) racc (~> 1.4) - nokogiri (1.13.8-x86_64-darwin) + nokogiri (1.18.2-x86_64-darwin) racc (~> 1.4) - nokogiri (1.13.8-x86_64-linux) + nokogiri (1.18.2-x86_64-linux-gnu) racc (~> 1.4) - racc (1.6.0) - rexml (3.2.5) - slather (2.7.2) + optparse (0.6.0) + os (1.1.4) + plist (3.7.2) + public_suffix (5.1.1) + racc (1.8.1) + rake (13.2.1) + representable (3.2.0) + declarative (< 0.1.0) + trailblazer-option (>= 0.1.1, < 0.2.0) + uber (< 0.2.0) + retriable (3.1.2) + rexml (3.4.0) + rouge (3.28.0) + ruby2_keywords (0.0.5) + rubyzip (2.4.1) + securerandom (0.3.2) + security (0.1.5) + signet (0.19.0) + addressable (~> 2.8) + faraday (>= 0.17.5, < 3.a) + jwt (>= 1.5, < 3.0) + multi_json (~> 1.10) + simctl (1.6.10) + CFPropertyList + naturally + slather (2.8.5) CFPropertyList (>= 2.2, < 4) activesupport clamp (~> 1.3) - nokogiri (~> 1.12) - xcodeproj (~> 1.21) + nokogiri (>= 1.14.3) + xcodeproj (~> 1.27) + sysrandom (1.0.5) + terminal-notifier (2.0.0) + terminal-table (3.0.2) + unicode-display_width (>= 1.1.1, < 3) + trailblazer-option (0.1.2) + tty-cursor (0.7.1) + tty-screen (0.8.2) + tty-spinner (0.9.3) + tty-cursor (~> 0.7) tzinfo (2.0.6) concurrent-ruby (~> 1.0) - xcodeproj (1.22.0) + uber (0.1.0) + unicode-display_width (2.6.0) + word_wrap (1.0.0) + xcodeproj (1.27.0) CFPropertyList (>= 2.3.3, < 4.0) atomos (~> 0.1.3) claide (>= 1.0.2, < 2.0) colored2 (~> 3.1) - nanaimo (~> 0.3.0) - rexml (~> 3.2.4) + nanaimo (~> 0.4.0) + rexml (>= 3.3.6, < 4.0) + xcpretty (0.4.0) + rouge (~> 3.28.0) + xcpretty-travis-formatter (1.0.1) + xcpretty (~> 0.2, >= 0.0.7) PLATFORMS arm64-darwin-21 + ruby x86_64-darwin-21 x86_64-linux DEPENDENCIES - nokogiri (>= 1.13.2) + fastlane + nokogiri slather BUNDLED WITH diff --git a/Makefile b/Makefile index b4c045d2..a24d8b07 100644 --- a/Makefile +++ b/Makefile @@ -6,12 +6,7 @@ else detected_OS := $(shell uname) endif -ifeq ($(detected_OS),Linux) # Linux only - prefix ?= ~/.local -else - prefix ?= /usr/local -endif - +prefix ?= ~/.local bindir ?= $(prefix)/bin libdir ?= $(prefix)/lib srcdir = Sources @@ -72,6 +67,7 @@ endif .PHONY: coverage coverage: test + @gem install bundler @bundle install @bundle exec slather coverage --ignore ../**/*/Xcode\* --ignore Tests/\* --scheme VariantsCore Variants.xcodeproj/ @@ -83,3 +79,8 @@ lint: validation: lint coverage @rm -rf variants.yml @echo "Ready to go." + +.PHONY: ci-validation +ci-validation: test + @rm -rf variants.yml + @echo "Ready to go." diff --git a/Package.swift b/Package.swift index 5bf38d9b..b030a0b9 100644 --- a/Package.swift +++ b/Package.swift @@ -17,7 +17,7 @@ let package = Package( ), .package( url: "https://github.com/jpsim/Yams.git", - from: "2.0.0" + from: "5.0.0" ), .package( name: "XcodeProj", @@ -26,7 +26,7 @@ let package = Package( ), .package( url: "https://github.com/apple/swift-argument-parser.git", - from: "0.1.0" + from: "1.0.0" ), .package( url: "https://github.com/stencilproject/Stencil.git", @@ -36,6 +36,10 @@ let package = Package( name: "danger-swift", url: "https://github.com/danger/swift.git", from: "3.5.0" + ), + .package( + url: "https://github.com/realm/SwiftLint", + from: "0.58.0" ) ], targets: [ @@ -57,7 +61,9 @@ let package = Package( ), .testTarget( name: "VariantsTests", - dependencies: ["Variants"] + dependencies: [ + "Variants" + ] ) ] ) diff --git a/README.md b/README.md index 524b8481..888a9980 100644 --- a/README.md +++ b/README.md @@ -43,11 +43,17 @@ This file is responsible for: ## Installation +### Dependencies + +In order to generate the code from templates, Variants requires Python 3. We recommend configuring the python version using a version management tool such as Pyenv. + +For details on how to install and use it check the [Pyenv repo](https://github.com/pyenv/pyenv). + ### On Github Actions CI See [Switching Variants on CI](docs/GITHUB_ACTION.md) for a better understanding and examples. -### Homebrew (recommended) +### Homebrew **(recommended)** ```sh brew install backbase/m/variants @@ -63,6 +69,16 @@ cd variants make install ``` +Once the installation is completed, the binary will be added to your `~/.local` folder. Make sure to have the folder included in your `PATH` variable. This should be done in the shell configuration file + +Add the following to the `~/.zshrc` or `~/.bashrc` file: +```sh +export PATH=$HOME/.local/bin:/usr/local/bin:$PATH +``` + +Make sure to restart your terminal or source the changed file with `source ~/.zshrc` or `source ~/.bashrc` + + ### Swift Package Manager #### Use as CLI diff --git a/Sources/Variants/main.swift b/Sources/Variants/main.swift index 642535b5..13bd9dc7 100644 --- a/Sources/Variants/main.swift +++ b/Sources/Variants/main.swift @@ -12,7 +12,7 @@ struct Variants: ParsableCommand { static var configuration = CommandConfiguration( commandName: "variants", abstract: "A command-line tool to setup deployment variants and working CI/CD setup", - version: "1.2.1", + version: "1.3.0", subcommands: [ Initializer.self, Setup.self, diff --git a/Sources/VariantsCore/Custom Types/Project/AndroidProject.swift b/Sources/VariantsCore/Custom Types/Project/AndroidProject.swift index 833553a9..ee7c0974 100644 --- a/Sources/VariantsCore/Custom Types/Project/AndroidProject.swift +++ b/Sources/VariantsCore/Custom Types/Project/AndroidProject.swift @@ -94,96 +94,88 @@ class AndroidProject: Project { } private func createVariants(with configuration: AndroidConfiguration, spec: String) throws { - guard let defaultVariant = configuration.variants - .first(where: { $0.name.lowercased() == "default" }) else { - throw ValidationError("Variant 'default' not found.") - } + let defaultVariant = try configuration.defaultVariant try gradleFactory.createScript(with: configuration, variant: defaultVariant) } - // swiftlint:disable function_body_length + // swiftlint:disable:next function_body_length private func setupFastlane(with configuration: AndroidConfiguration, skip: Bool) { - if skip { - Logger.shared.logInfo("Skipped Fastlane setup", item: "") - } else { - Logger.shared.logInfo("Setting up Fastlane", item: "") - - do { - let projectSourceFolder = configuration.path - let path = try TemplateDirectory().path - try Bash("cp", arguments: "-R", "\(path.absolute())/android/_fastlane/", ".") - .run() - - let baseSetupCompletedMessage = - """ - ✅ Your variants configuration was setup - ✅ For configuration properties with 'project' destination, they have been - stored in '\(projectSourceFolder)/gradleScripts/variants.gradle'. - This gradle file should be used by your 'app/build.gradle' in order to read the app's - information and custom properties you've set with destination 'project'. - 🔄 Use 'variants switch --variants ' to switch between variants and - update the properties in the files described above. - - That is all. - """ - - var setupCompleteMessage = + guard skip == false else { + return Logger.shared.logInfo("Skipped Fastlane setup for Android", item: "") + } + + Logger.shared.logInfo("Setting up Fastlane for Android", item: "") + do { + let projectSourceFolder = configuration.path + let path = try TemplateDirectory().path + try Bash("cp", arguments: "-R", "\(path.absolute())/android/_fastlane/", ".") + .run() + + let baseSetupCompletedMessage = + """ + ✅ Your variants configuration was setup + ✅ For configuration properties with 'project' destination, they have been + stored in '\(projectSourceFolder)/gradleScripts/variants.gradle'. + This gradle file should be used by your 'app/build.gradle' in order to read the app's + information and custom properties you've set with destination 'project'. + 🔄 Use 'variants switch --variants ' to switch between variants and + update the properties in the files described above. + + That is all. + """ + + var setupCompleteMessage = + """ + + We got almost everything done! + + ❌ Fastlane could not be setup. The template wasn't found or something else went wrong when + copying it. + + """ + + if StaticPath.Fastlane.baseFolder.isDirectory { + let defaultVariant = try configuration.defaultVariant + + // Create 'variants_params.rb' with parameters whose + // destination are set as '.fastlane' + try storeFastlaneParams(for: defaultVariant, configuration: configuration) + + setupCompleteMessage = """ - We got almost everything done! + Your setup is complete, congratulations! 🎉 + However, you still need to provide some parameters in order for fastlane to run correctly. - ❌ Fastlane could not be setup. The template wasn't found or something else went wrong when - copying it. + ⚠️ Check the files in 'fastlane/parameters/', change the parameters + accordingly, provide environment variables when applicable. + ⚠️ Note that the values in the file 'fastlane/parameters/variants_params.rb' + where generated automatically for configuration properties with 'fastlane' destination. """ - - if StaticPath.Fastlane.baseFolder.isDirectory { - guard let defaultVariant = configuration.variants - .first(where: { $0.name.lowercased() == "default" }) else { - throw ValidationError("Variant 'default' not found.") - } - - // Create 'variants_params.rb' with parameters whose - // destination are set as '.fastlane' - try storeFastlaneParams(for: defaultVariant, configuration: configuration) - - setupCompleteMessage = - """ - - Your setup is complete, congratulations! 🎉 - However, you still need to provide some parameters in order for fastlane to run correctly. - - ⚠️ Check the files in 'fastlane/parameters/', change the parameters - accordingly, provide environment variables when applicable. - ⚠️ Note that the values in the file 'fastlane/parameters/variants_params.rb' - where generated automatically for configuration properties with 'fastlane' destination. - - """ - - Logger.shared.logInfo("🚀 ", item: "Fastlane setup with success", color: .green) - Logger.shared.logInfo("👇 Next steps ", item: "", color: .yellow) - } else { - Logger.shared.logWarning("", item: "Fastlane setup couldn't be completed") - Logger.shared.logInfo("👇 What happened ", item: "", color: .yellow) - } - - setupCompleteMessage += baseSetupCompletedMessage - setupCompleteMessage.enumerateLines { (line, _) in - Logger.shared.logInfo("", item: line, color: .yellow) - } - - } catch let error as ValidationError { - Logger.shared.logFatal(item: error.description) - - } catch let error as RuntimeError { - Logger.shared.logFatal(item: error.description) - - } catch { - Logger.shared.logFatal(item: error.localizedDescription) + + Logger.shared.logInfo("🚀 ", item: "Fastlane setup with success", color: .green) + Logger.shared.logInfo("👇 Next steps ", item: "", color: .yellow) + } else { + Logger.shared.logWarning("", item: "Fastlane setup couldn't be completed") + Logger.shared.logInfo("👇 What happened ", item: "", color: .yellow) } + + setupCompleteMessage += baseSetupCompletedMessage + setupCompleteMessage.enumerateLines { (line, _) in + Logger.shared.logInfo("", item: line, color: .yellow) + } + + } catch let error as ValidationError { + Logger.shared.logFatal(item: error.description) + + } catch let error as RuntimeError { + Logger.shared.logFatal(item: error.description) + + } catch { + Logger.shared.logFatal(item: error.localizedDescription) } } - // swiftlint:enable function_body_length private func storeFastlaneParams(for variant: AndroidVariant, configuration: AndroidConfiguration) throws { var customProperties: [CustomProperty] = (variant.custom ?? []) + (configuration.custom ?? []) diff --git a/Sources/VariantsCore/Custom Types/Project/iOSProject.swift b/Sources/VariantsCore/Custom Types/Project/iOSProject.swift index e529f7ca..fc2ecd7f 100644 --- a/Sources/VariantsCore/Custom Types/Project/iOSProject.swift +++ b/Sources/VariantsCore/Custom Types/Project/iOSProject.swift @@ -5,12 +5,12 @@ // Created by Balazs Toth // +// swiftlint:disable type_name + import Foundation import ArgumentParser import PathKit -// swiftlint:disable type_name - class iOSProject: Project { init( specHelper: SpecHelper, @@ -81,33 +81,25 @@ class iOSProject: Project { private func switchTo(_ variant: iOSVariant, spec: String, configuration: iOSConfiguration) throws { specHelper.logger.logInfo(item: "Found: \(variant.title)") - try configuration.targets - .map { (key: $0.key, value: $0.value)} - .forEach { namedTarget in - - // Create 'variants.xcconfig' with parameters whose - // destination are set as '.project' - do { - try configFactory.createConfig( - with: namedTarget, - variant: variant, - xcodeProj: configuration.xcodeproj, - configPath: Path(spec).absolute().parent(), - addToXcodeProj: false - ) - } catch { - Logger.shared.logFatal(item: error.localizedDescription) - } - - var customProperties: [CustomProperty] = (variant.custom ?? []) + (configuration.custom ?? []) - customProperties.append(variant.destinationProperty) - - // Create 'variants_params.rb' with parameters whose - // destination are set as '.fastlane' - try? storeFastlaneParams(customProperties) - - try parametersFactory.createMatchFile(using: variant, target: namedTarget.value) - } + // Create 'variants.xcconfig' with parameters whose + // destination are set as '.project' + let configPath = Path(spec).absolute().parent() + do { + try configFactory.createConfig( + for: variant, + configuration: configuration, + configPath: configPath) + } catch { + Logger.shared.logFatal(item: error.localizedDescription) + } + + // Update `variants_params.rb` with custom fastlane properties + var customProperties: [CustomProperty] = (variant.custom ?? []) + (configuration.custom ?? []) + customProperties.append(variant.destinationProperty) + try storeFastlaneParams(customProperties) + + // Update `Matchfile` with signing configurations + try parametersFactory.createMatchFile(for: variant, configuration: configuration) } private func runPostSwitchScript(_ script: String) throws { @@ -116,116 +108,99 @@ class iOSProject: Project { } private func createVariants(with configuration: iOSConfiguration, spec: String) throws { - try configuration.targets - .map { (key: $0.key, value: $0.value) } - .forEach { target in - - guard let defaultVariant = configuration.variants - .first(where: { $0.name.lowercased() == "default" }) else { - throw ValidationError("Variant 'default' not found.") - } - - // Create 'variants.xcconfig' with parameters whose - // destination are set as '.project' - let configPath = Path(spec).absolute().parent() - do { - try configFactory.createConfig(with: target, - variant: defaultVariant, - xcodeProj: configuration.xcodeproj, - configPath: configPath, - addToXcodeProj: true) - } catch { - Logger.shared.logFatal(item: error.localizedDescription) - } - } + let defaultVariant = try configuration.defaultVariant + + // Create 'variants.xcconfig' with parameters whose + // destination are set as '.project' + let configPath = Path(spec).absolute().parent() + do { + try configFactory.createConfig( + for: defaultVariant, + configuration: configuration, + configPath: configPath) + } catch { + Logger.shared.logFatal(item: error.localizedDescription) + } } - // swiftlint:disable function_body_length + // swiftlint:disable:next function_body_length private func setupFastlane(with configuration: iOSConfiguration, skip: Bool) { - if skip { - Logger.shared.logInfo("Skipped Fastlane setup", item: "") - } else { - Logger.shared.logInfo("Setting up Fastlane", item: "") - - do { - let path = try TemplateDirectory().path - try Bash("cp", arguments: "-R", "\(path.absolute())/ios/_fastlane/", ".") - .run() - - let projectSourceFolder = configuration.targets.first?.value.source.path ?? "{{ SOURCE_PATH }}" - let baseSetupCompletedMessage = - """ - ✅ Your variants configuration was setup - ✅ '\(projectSourceFolder)/Variants/' has been created. - Add that folder to your Xcode project if it wasn't done automatically. - ✅ For configuration properties with 'project' destination, they have been - stored in '\(projectSourceFolder)/Variants/variants.xcconfig'. - These values have been made available to your project via your Info.plist. - Use them in your code as 'Variants.configuration["SAMPLE_PROPERTY"]'. - 🔄 Use 'variants switch --variants ' to switch between variants and - update the properties in the files described above. - - That is all. - """ - - var setupCompleteMessage = + guard skip == false else { + return Logger.shared.logInfo("Skipped Fastlane setup for iOS", item: "") + } + + Logger.shared.logInfo("Setting up Fastlane for iOS", item: "") + do { + let path = try TemplateDirectory().path + try Bash("cp", arguments: "-R", "\(path.absolute())/ios/_fastlane/", ".") + .run() + + let projectSourceFolder = configuration.target.source.path + let baseSetupCompletedMessage = + """ + ✅ Your variants configuration was setup + ✅ '\(projectSourceFolder)/Variants/' has been created. + Add that folder to your Xcode project if it wasn't done automatically. + ✅ For configuration properties with 'project' destination, they have been + stored in '\(projectSourceFolder)/Variants/variants.xcconfig'. + These values have been made available to your project via your Info.plist. + Use them in your code as 'Variants.configuration["SAMPLE_PROPERTY"]'. + 🔄 Use 'variants switch --variants ' to switch between variants and + update the properties in the files described above. + + That is all. + """ + + var setupCompleteMessage = + """ + + We got almost everything done! + + ❌ Fastlane could not be setup. The template wasn't found or something else went wrong when + copying it. + + """ + + if StaticPath.Fastlane.baseFolder.isDirectory { + let defaultVariant = try configuration.defaultVariant + + // Update `variants_params.rb` with custom fastlane properties + var customProperties: [CustomProperty] = (defaultVariant.custom ?? []) + (configuration.custom ?? []) + customProperties.append(defaultVariant.destinationProperty) + try storeFastlaneParams(customProperties) + + // Update `Matchfile` with signing configurations + try parametersFactory.createMatchFile(for: defaultVariant, configuration: configuration) + + setupCompleteMessage = """ - We got almost everything done! + Your setup is complete, congratulations! 🎉 + However, you still need to provide some parameters in order for fastlane to run correctly. - ❌ Fastlane could not be setup. The template wasn't found or something else went wrong when - copying it. + ⚠️ Check the files in 'fastlane/parameters/', change the parameters accordingly, + provide environment variables when applicable. + ⚠️ If you use Cocoapods-art, enable it in 'fastlane/Cocoapods' + ⚠️ Change your signing configuration in 'fastlane/Match' and potentially 'fastlane/Deploy' """ - - if StaticPath.Fastlane.baseFolder.isDirectory { - - guard let defaultVariant = configuration.variants - .first(where: { $0.name.lowercased() == "default" }), - let namedTarget = configuration.targets.first - else { - throw ValidationError("Variant 'default' not found.") - } - var customProperties: [CustomProperty] = (defaultVariant.custom ?? []) + (configuration.custom ?? []) - customProperties.append(defaultVariant.destinationProperty) - - // Create 'variants_params.rb' with parameters whose - // destination are set as '.fastlane' - try storeFastlaneParams(customProperties) - - try parametersFactory.createMatchFile(using: defaultVariant, target: namedTarget.value) - - setupCompleteMessage = - """ - - Your setup is complete, congratulations! 🎉 - However, you still need to provide some parameters in order for fastlane to run correctly. - - ⚠️ Check the files in 'fastlane/parameters/', change the parameters accordingly, - provide environment variables when applicable. - ⚠️ If you use Cocoapods-art, enable it in 'fastlane/Cocoapods' - ⚠️ Change your signing configuration in 'fastlane/Match' and potentially 'fastlane/Deploy' - - """ - - Logger.shared.logInfo("🚀 ", item: "Fastlane setup with success", color: .green) - Logger.shared.logInfo("👇 Next steps ", item: "", color: .yellow) - } else { - Logger.shared.logWarning("", item: "Fastlane setup couldn't be completed") - Logger.shared.logInfo("👇 What happened ", item: "", color: .yellow) - } - - setupCompleteMessage += baseSetupCompletedMessage - setupCompleteMessage.enumerateLines { (line, _) in - Logger.shared.logInfo("", item: line, color: .yellow) - } - - } catch { - Logger.shared.logFatal(item: error.localizedDescription) + + Logger.shared.logInfo("🚀 ", item: "Fastlane setup with success", color: .green) + Logger.shared.logInfo("👇 Next steps ", item: "", color: .yellow) + } else { + Logger.shared.logWarning("", item: "Fastlane setup couldn't be completed") + Logger.shared.logInfo("👇 What happened ", item: "", color: .yellow) } + + setupCompleteMessage += baseSetupCompletedMessage + setupCompleteMessage.enumerateLines { (line, _) in + Logger.shared.logInfo("", item: line, color: .yellow) + } + + } catch { + Logger.shared.logFatal(item: error.localizedDescription) } } - // swiftlint:enable function_body_length private func storeFastlaneParams(_ properties: [CustomProperty]) throws { let fastlaneProperties = properties.filter { $0.destination == .fastlane } diff --git a/Sources/VariantsCore/Custom Types/TemplateDirectory.swift b/Sources/VariantsCore/Custom Types/TemplateDirectory.swift index c5e232c6..675a687b 100644 --- a/Sources/VariantsCore/Custom Types/TemplateDirectory.swift +++ b/Sources/VariantsCore/Custom Types/TemplateDirectory.swift @@ -15,13 +15,15 @@ struct TemplateDirectory { init( directories: [String] = [ - "/usr/local/lib/variants/templates", + // Uncomment below line while in development to read from local template files + "../../../Templates", + "~/.local/lib/variants/templates", "./Templates" ] ) throws { - var templateDirectories = directories.map(Path.init(stringLiteral:)) - + var templateDirectories = directories.map { Path($0).absolute() } + if let variantsInstallationPath = try? Bash( "which", arguments: "variants" @@ -33,7 +35,7 @@ struct TemplateDirectory { )) ) } - + let firstFoundDirectory = templateDirectories.first(where: \.exists) guard let path = firstFoundDirectory else { let dirs = directories.joined(separator: " or ") diff --git a/Sources/VariantsCore/Custom Types/UtilsDirectory.swift b/Sources/VariantsCore/Custom Types/UtilsDirectory.swift index bff79d84..ea53bccb 100644 --- a/Sources/VariantsCore/Custom Types/UtilsDirectory.swift +++ b/Sources/VariantsCore/Custom Types/UtilsDirectory.swift @@ -15,11 +15,11 @@ struct UtilsDirectory { init( directories: [String] = [ - "/usr/local/lib/variants/utils", + "~/.local/lib/variants/utils", "./utils" ] ) throws { - var utilsDirectories = directories.map(Path.init(stringLiteral:)) + var utilsDirectories = directories.map { Path($0).absolute() } if let variantsInstallationPath = try? Bash( "which", diff --git a/Sources/VariantsCore/Extensions/KeyedDecodingContainer+EnvVar.swift b/Sources/VariantsCore/Extensions/KeyedDecodingContainer+EnvVar.swift index fda61dd5..1f333342 100644 --- a/Sources/VariantsCore/Extensions/KeyedDecodingContainer+EnvVar.swift +++ b/Sources/VariantsCore/Extensions/KeyedDecodingContainer+EnvVar.swift @@ -28,7 +28,6 @@ public extension KeyedDecodingContainer { } fileprivate extension String { - var asEnvVariable: String? { get throws { let regexPattern = #"^\$\{\{ envVars.(?.*) \}\}"# diff --git a/Sources/VariantsCore/Factory/FastlaneParametersFactory.swift b/Sources/VariantsCore/Factory/FastlaneParametersFactory.swift index ab357082..ec9c67da 100644 --- a/Sources/VariantsCore/Factory/FastlaneParametersFactory.swift +++ b/Sources/VariantsCore/Factory/FastlaneParametersFactory.swift @@ -11,7 +11,7 @@ import PathKit protocol ParametersFactory { func createParametersFile(in file: Path, renderTemplate: String, with parameters: [CustomProperty]) throws - func createMatchFile(using variant: iOSVariant, target: iOSTarget) throws + func createMatchFile(for variant: iOSVariant, configuration: iOSConfiguration) throws func render(context: [String: Any], renderTemplate: String) throws -> Data? func write(_ data: Data, using parametersFile: Path) throws } @@ -31,24 +31,29 @@ class FastlaneParametersFactory: ParametersFactory { try write(data, using: file) } - func createMatchFile(using variant: iOSVariant, target: iOSTarget) throws { + func createMatchFile(for variant: iOSVariant, configuration: iOSConfiguration) throws { // Return immediately if folder 'fastlane/' doesn't exist. guard StaticPath.Fastlane.baseFolder.exists && StaticPath.Fastlane.baseFolder.isDirectory else { return } // Populate 'fastlane/parameters/match_params.rb' from template - let parameters: [CustomProperty] = variant.signing?.customProperties() ?? [] + let parameters: [CustomProperty] = variant.releaseSigning?.customProperties() ?? [] try? createParametersFile(in: StaticPath.Fastlane.matchParametersFile, renderTemplate: StaticPath.Template.matchParametersFileName, with: parameters) // Populate 'fastlane/Matchfile' from template - var context = [ - "export_method": (variant.signing?.exportMethod ?? .appstore).rawValue, - "bundle_id": variant.makeBundleID(for: target) + let extensionBundleIDs = configuration.extensions + .filter { $0.signed } + .map { $0.makeBundleID(variant: variant, target: configuration.target) } + .reduce(into: [], { $0.append($1) }) + let appBundleID = [variant.makeBundleID(for: configuration.target)] + var context: [String: Any] = [ + "export_method": (variant.releaseSigning?.exportMethod ?? .appstore).rawValue, + "app_identifiers": appBundleID + extensionBundleIDs ] - if let matchURL = variant.signing?.matchURL { + if let matchURL = variant.releaseSigning?.matchURL { context["git_url"] = matchURL } else { Logger.shared.logWarning(item: diff --git a/Sources/VariantsCore/Factory/iOS/VariantsFileFactory.swift b/Sources/VariantsCore/Factory/iOS/VariantsFileFactory.swift index 75ec6b28..f22ff560 100644 --- a/Sources/VariantsCore/Factory/iOS/VariantsFileFactory.swift +++ b/Sources/VariantsCore/Factory/iOS/VariantsFileFactory.swift @@ -24,20 +24,15 @@ class VariantsFileFactory { let path = try TemplateDirectory().path guard let variantsGybTemplatePath = try? path.safeJoin(path: Path("ios/")) else { return } - let secrets = variant.custom?.secrets() ?? [] - let configurationValues = variant.custom?.configurationValues() ?? [] + let context = [ - "secrets": secrets, - "configurationValues": configurationValues + "secrets": variant.custom?.projectSecretConfigurationValues ?? [], + "configurationValues": variant.custom?.projectConfigurationValues ?? [] ] as [String: Any] - let environment = Environment(loader: FileSystemLoader(paths: [variantsGybTemplatePath.absolute()])) - let rendered = try environment.renderTemplate(name: StaticPath.Template.variantsSwiftGybFileName, + let content = try environment.renderTemplate(name: StaticPath.Template.variantsSwiftGybFileName, context: context) - // Replace multiple empty lines by one only - let lines = rendered.split(whereSeparator: \.isNewline) - let content = lines.joined(separator: "\n") - + try write(Data(content.utf8), using: configFilePath.parent().absolute()) let variantsGybFile = try configFilePath.parent().absolute() .safeJoin(path: Path(StaticPath.Xcode.variantsGybFileName)) @@ -53,59 +48,69 @@ class VariantsFileFactory { } private func write(_ data: Data, using folder: Path = Path("/tmp/")) throws { - if folder.isDirectory, folder.exists { - let variantsGybFile = try folder.safeJoin(path: Path(StaticPath.Xcode.variantsGybFileName)) - - // Only proceed to write to file if such doesn't yet exist - // Or does exist and 'isWritable' - guard !variantsGybFile.exists - || variantsGybFile.isWritable else { - throw TemplateDoesNotExist(templateNames: [folder.string]) - } - - // Write to file - try variantsGybFile.write(data) - - if - try UtilsDirectory().path.exists, - let gybExecutablePath = try? UtilsDirectory().path.safeJoin(path: "gyb"), - let fileContent = try? variantsGybFile.read(), - fileContent == data { - - try Bash(gybExecutablePath.absolute().description, - arguments: - "--line-directive", - "", - "-o", - "Variants.swift", - variantsGybFile.absolute().description - ).run() - - logger.logInfo("⚙️ ", item: """ - '\(variantsGybFile.parent().abbreviate().string)/Variants.swift' has been generated with success - """, color: .green) - } - } else { + guard folder.isDirectory, folder.exists else { + throw TemplateDoesNotExist(templateNames: [folder.string]) + } + + let variantsGybFile = try folder.safeJoin(path: Path(StaticPath.Xcode.variantsGybFileName)) + // Only proceed to write to file if such doesn't yet exist + // Or does exist and 'isWritable' + guard !variantsGybFile.exists || variantsGybFile.isWritable else { throw TemplateDoesNotExist(templateNames: [folder.string]) } + + try variantsGybFile.write(data) + guard + try UtilsDirectory().path.exists, + let gybExecutablePath = try? UtilsDirectory().path.safeJoin(path: "gyb"), + let fileContent = try? variantsGybFile.read(), + fileContent == data + else { return } + + let variantsOutputFilePath = "\(variantsGybFile.parent().absolute().string)/Variants.swift" + let gybStdErr = try Bash(gybExecutablePath.absolute().description, + arguments: + "--line-directive", + "", + "-o", + variantsOutputFilePath, + variantsGybFile.absolute().description + ).capture(stream: .stderr) + handleGybErrors(message: gybStdErr, variantsFilePath: variantsOutputFilePath) + logger.logInfo("⚙️ ", item: "'\(variantsOutputFilePath)' has been generated with success", color: .green) } - let logger: Logger -} + private func handleGybErrors(message: String?, variantsFilePath: String) { + guard let message, !message.isEmpty else { return } -fileprivate extension Sequence where Iterator.Element == CustomProperty { - func secrets() -> [CustomProperty] { - return self - .filter({ $0.destination == .project && $0.isEnvironmentVariable }) - .map { (property) -> CustomProperty in - return CustomProperty(name: property.name, - value: "os.environ.get('"+property.environmentValue+"')", - destination: property.destination) - } + switch message { + case _ where message.contains("env: python3: No such file or directory"): + logger.logFatal(item: + """ + We're unable to find a 'python3' executable. + Install 'python3' or ensure it's in your executables path and try running this Variants command again. + Tip: + * Install pyenv (brew install pyenv) + * Install python3 (pyenv install python3) + * Add "$(pyenv root)/shims" to your PATH + """) + case _ where message.contains("for chunk in chunks(encode(os.environ.get("): + logger.logFatal(item: + """ + We're unable to create 'Variants.Secrets' in '\(variantsFilePath)'. + Ensure that custom config values whose `env: true` are actually environment variables. + """) + case _ where message.contains("pyenv: python2.7: command not found"): + logger.logFatal(item: + """ + Looks like you have pyenv installed but the current configured version is not correct. + Please, select the latest build of python 3 as local version. + For example: `pyenv local 3` + """) + default: + logger.logFatal(item: message as Any) + } } - func configurationValues() -> [CustomProperty] { - return self - .filter({ $0.destination == .project && !$0.isEnvironmentVariable }) - } + let logger: Logger } diff --git a/Sources/VariantsCore/Factory/iOS/XCConfigFactory.swift b/Sources/VariantsCore/Factory/iOS/XCConfigFactory.swift index 69d1eeb5..99327ae3 100644 --- a/Sources/VariantsCore/Factory/iOS/XCConfigFactory.swift +++ b/Sources/VariantsCore/Factory/iOS/XCConfigFactory.swift @@ -5,6 +5,8 @@ // Created by Arthur Alves // +// swiftlint:disable file_length + import Foundation import ArgumentParser import PathKit @@ -15,14 +17,21 @@ public typealias DoesFileExist = (exists: Bool, path: Path?) protocol XCFactory { func write(_ stringContent: String, toFile file: Path, force: Bool) -> (Bool, Path?) func writeJSON(_ encodableObject: T, toFile file: Path) -> (Bool, Path?) where T: Encodable - func createConfig(with target: NamedTarget, - variant: iOSVariant, - xcodeProj: String?, - configPath: Path, - addToXcodeProj: Bool?) throws + func createConfig(for variant: iOSVariant, configuration: iOSConfiguration, configPath: Path) throws } class XCConfigFactory: XCFactory { + private enum PListKey { + static let productBundleID = "PRODUCT_BUNDLE_IDENTIFIER" + static let productName = "PRODUCT_NAME" + static let assetCatalogAppIconName = "ASSETCATALOG_COMPILER_APPICON_NAME" + static let testHost = "TEST_HOST" + static let provisioningProfile = "PROVISIONING_PROFILE_SPECIFIER" + static let codeSignStyle = "CODE_SIGN_STYLE" + static let codeSignIdentity = "CODE_SIGN_IDENTITY" + static let developmentTeam = "DEVELOPMENT_TEAM" + } + init(logger: Logger = Logger(verbose: false)) { self.logger = logger } @@ -63,20 +72,11 @@ class XCConfigFactory: XCFactory { } } - func createConfig(with target: NamedTarget, - variant: iOSVariant, - xcodeProj: String?, - configPath: Path, - addToXcodeProj: Bool? = true) throws { - + func createConfig(for variant: iOSVariant, configuration: iOSConfiguration, configPath: Path) throws { let logger = Logger.shared - guard let xcodeProj = xcodeProj - else { - throw RuntimeError("Attempting to create \(xcconfigFileName) - Path to Xcode Project not found") - } - let xcodeProjPath = Path(xcodeProj) - let configString = target.value.source.config - + let xcodeProjPath = Path(configuration.xcodeproj) + let configString = configuration.target.source.config + logger.logInfo("Checking if \(xcconfigFileName) exists", item: "") let xcodeConfigFolder = Path("\(configPath)/\(configString)") guard xcodeConfigFolder.isDirectory else { @@ -91,27 +91,33 @@ class XCConfigFactory: XCFactory { _ = write("", toFile: xcodeConfigPath, force: true) logger.logInfo("Created file: ", item: "'\(xcconfigFileName)' at \(xcodeConfigPath.parent().abbreviate().description)") - populateConfig(with: target, configFile: xcodeConfigPath, variant: variant) + populateConfig(for: configuration.target, configFile: xcodeConfigPath, variant: variant) /* * If template files should be added to Xcode Project */ - if addToXcodeProj ?? false { - addToXcode(xcodeConfigPath, toProject: xcodeProjPath, sourceRoot: configPath, target: target, variant: variant) - } + addToXcode(xcodeConfigPath, toProject: xcodeProjPath, sourceRoot: configPath, variant: variant, configuration: configuration) /* * Adjust signing configuration in project.pbxproj */ - updateSigningConfig(for: variant, inTarget: target, projectPath: xcodeProjPath) + updateSigning(using: variant.releaseSigning, targetName: configuration.target.source.info, + isRelease: true, projectPath: xcodeProjPath) + updateSigning(using: variant.debugSigning, targetName: configuration.target.source.info, + isRelease: false, projectPath: xcodeProjPath) + + updateSigningConfigForExtensions(signing: variant.releaseSigning, variant: variant, configuration: configuration, + isRelease: true, projectPath: xcodeProjPath) + updateSigningConfigForExtensions(signing: variant.debugSigning, variant: variant, configuration: configuration, + isRelease: false, projectPath: xcodeProjPath) /* * INFO.plist */ - let infoPath = target.value.source.info + let infoPath = configuration.target.source.info let infoPlistPath = Path("\(configPath)/\(infoPath)") - updateInfoPlist(with: target.value, configFile: infoPlistPath, variant: variant) - + updateInfoPlist(with: configuration.target, configFile: infoPlistPath, variant: variant) + /* * Add custom properties whose values should be read from environment variables * to `Variants.Secret` as encrypted secrets. @@ -125,8 +131,8 @@ class XCConfigFactory: XCFactory { private func addToXcode(_ xcConfigFile: Path, toProject projectPath: Path, sourceRoot: Path, - target: NamedTarget, - variant: iOSVariant) { + variant: iOSVariant, + configuration: iOSConfiguration) { let variantsFile = Path("\(xcConfigFile.parent().absolute().description)/Variants.swift") do { let path = try TemplateDirectory().path @@ -136,30 +142,39 @@ class XCConfigFactory: XCFactory { ).run() let xcodeFactory = XcodeProjFactory() - xcodeFactory.add([xcConfigFile, variantsFile], toProject: projectPath, sourceRoot: sourceRoot, target: target) - + xcodeFactory.add([xcConfigFile, variantsFile], toProject: projectPath, sourceRoot: sourceRoot, target: configuration.target) + + // Update main target let mainTargetSettings = [ - "PRODUCT_BUNDLE_IDENTIFIER": "$(V_BUNDLE_ID)", - "PRODUCT_NAME": "$(V_APP_NAME)", - "ASSETCATALOG_COMPILER_APPICON_NAME": "$(V_APP_ICON)" + PListKey.productBundleID: "$(V_BUNDLE_ID)", + PListKey.productName: "$(V_APP_NAME)", + PListKey.assetCatalogAppIconName: "$(V_APP_ICON)" ] - xcodeFactory.modify(mainTargetSettings, in: projectPath, target: target.value) - - xcodeFactory.modify( - [ - "TEST_HOST": "$(BUILT_PRODUCTS_DIR)/$(V_APP_NAME).app/$(V_APP_NAME)" - ], - in: projectPath, - target: target.value, - asTestSettings: true) + xcodeFactory.modify(mainTargetSettings, in: projectPath, targetName: configuration.target.source.info) + + // Update test target + let testTargetSettings = [ + PListKey.testHost: "$(BUILT_PRODUCTS_DIR)/$(V_APP_NAME).app/$(V_APP_NAME)" + ] + xcodeFactory.modify(testTargetSettings, in: projectPath, targetName: configuration.target.testTarget) + + // Update extensions + for targetExtension in configuration.extensions.filter({ $0.signed }) { + let bundleID = targetExtension.makeBundleID(variant: variant, target: configuration.target) + let extensionSettings = [ + PListKey.productBundleID: "\(bundleID)" + ] + xcodeFactory.modify(extensionSettings, in: projectPath, targetName: targetExtension.name) + } + } catch { logger.logError("❌ ", item: "Failed to add Variants.swift to Xcode Project") } } - private func populateConfig(with target: NamedTarget, configFile: Path, variant: iOSVariant) { + private func populateConfig(for target: iOSTarget, configFile: Path, variant: iOSVariant) { logger.logInfo("Populating: ", item: "'\(configFile.lastComponent)'") - variant.getDefaultValues(for: target.value).forEach { (key, value) in + variant.getDefaultValues(for: target).forEach { (key, value) in let stringContent = "\(key) = \(value)" logger.logDebug("Item: ", item: stringContent, indentationLevel: 1, color: .purple) @@ -170,32 +185,96 @@ class XCConfigFactory: XCFactory { } } - private func updateSigningConfig( - for variant: iOSVariant, - inTarget target: NamedTarget, + private func updateSigning( + using signing: iOSSigning?, + targetName: String, + isRelease: Bool, projectPath: Path ) { guard - let exportMethod = variant.signing?.exportMethod, - let teamName = variant.signing?.teamName, - let teamID = variant.signing?.teamID, - !teamID.isEmpty, - !teamName.isEmpty + let signing, + let teamID = signing.teamID, !teamID.isEmpty else { return } - let xcodeFactory = XcodeProjFactory() - var certType = "Development" - if exportMethod == .appstore || exportMethod == .enterprise { - certType = "Distribution" + var signingSettings = [ + PListKey.provisioningProfile: "", + PListKey.codeSignStyle: "\(signing.style.rawValue.capitalized)", + PListKey.developmentTeam: "\(teamID)" + ] + + if signing.style == .manual { + guard + let exportMethod = signing.exportMethod, + let teamName = signing.teamName, !teamName.isEmpty + else { return } + + signingSettings[PListKey.provisioningProfile] = "$(V_MATCH_PROFILE)" + + if signing.autoDetectSigningIdentity, + let fetchedSigningIdentity = signing.codeSigningIdentity { + signingSettings[PListKey.codeSignIdentity] = fetchedSigningIdentity + } else { + signingSettings[PListKey.codeSignIdentity] = "Apple \(exportMethod.certType): \(teamName) (\(teamID))" + } } - let mainTargetSettings = [ - "PROVISIONING_PROFILE_SPECIFIER": "$(V_MATCH_PROFILE)", - "CODE_SIGN_STYLE": "Manual", - "CODE_SIGN_IDENTITY": "Apple \(certType): \(teamName) (\(teamID))" + + let xcodeFactory = XcodeProjFactory() + xcodeFactory.modify( + signingSettings, + in: projectPath, + targetName: targetName, + configurationTypes: [isRelease ? .release : .debug]) + } + + private func updateSigningConfigForExtensions( + signing: iOSSigning?, + variant: iOSVariant, + configuration: iOSConfiguration, + isRelease: Bool, + projectPath: Path + ) { + let targetExtensions = configuration.extensions.filter({ $0.signed }) + + guard + !targetExtensions.isEmpty, + let signing, + let teamID = signing.teamID, !teamID.isEmpty + else { return } + + var signingSettings = [ + PListKey.provisioningProfile: "", + PListKey.codeSignStyle: "\(signing.style.rawValue.capitalized)", + PListKey.developmentTeam: "\(teamID)" ] - xcodeFactory.modify(mainTargetSettings, in: projectPath, target: target.value) + + if signing.style == .manual { + guard + let exportMethod = signing.exportMethod, + let teamName = signing.teamName, !teamName.isEmpty + else { return } + + if signing.autoDetectSigningIdentity, let fetchedSigningIdentity = signing.codeSigningIdentity { + signingSettings[PListKey.codeSignIdentity] = fetchedSigningIdentity + } else { + signingSettings[PListKey.codeSignIdentity] = "Apple \(exportMethod.certType): \(teamName) (\(teamID))" + } + } + + let xcodeFactory = XcodeProjFactory() + for targetExtension in targetExtensions { + if signing.style == .manual, let exportMethod = signing.exportMethod { + let bundleID = targetExtension.makeBundleID(variant: variant, target: configuration.target) + signingSettings[PListKey.provisioningProfile] = "\(exportMethod.prefix) \(bundleID)" + } + + xcodeFactory.modify( + signingSettings, + in: projectPath, + targetName: targetExtension.name, + configurationTypes: [isRelease ? .release : .debug]) + } } - + private func updateInfoPlist(with target: iOSTarget, configFile: Path, variant: iOSVariant) { let configFilePath = configFile.absolute().description do { @@ -231,3 +310,5 @@ class XCConfigFactory: XCFactory { let xcconfigFileName: String = "variants.xcconfig" let logger: Logger } + +// swiftlint:enable file_length diff --git a/Sources/VariantsCore/Factory/iOS/XcodeProjFactory.swift b/Sources/VariantsCore/Factory/iOS/XcodeProjFactory.swift index 9dcf19d4..c29862dd 100644 --- a/Sources/VariantsCore/Factory/iOS/XcodeProjFactory.swift +++ b/Sources/VariantsCore/Factory/iOS/XcodeProjFactory.swift @@ -5,17 +5,19 @@ // Created by Arthur Alves // -// swiftlint:disable file_length - import Foundation import XcodeProj import PathKit struct XcodeProjFactory { + enum BuildConfigType: String, CaseIterable { + case debug, release + } + private let logger: Logger - init(logLegel: Bool = false) { - logger = Logger(verbose: logLegel) + init(enableVerboseLog: Bool = true) { + logger = Logger(verbose: enableVerboseLog) } /// Scan the working directory for a Xcode project @@ -109,26 +111,18 @@ struct XcodeProjFactory { /// - projectPath: Path to `.xcodeproj` /// - sourceRoot: Path to source root group the files will be added to /// - target: Named target `(key: String, value: Target) ` these files will be added to - func add(_ files: [Path], toProject projectPath: Path, sourceRoot: Path, target: NamedTarget) { + func add(_ files: [Path], toProject projectPath: Path, sourceRoot: Path, target: iOSTarget) { do { let project = try XcodeProj(path: projectPath) - let variantsGroup = try createVarientsGroup(for: project, path: projectPath, sourceRoot: sourceRoot, target: target) for file in files { - try add( - file: file, - to: project, - path: projectPath, - variantsGroup: variantsGroup, - sourceRoot: sourceRoot, - target: target - ) + try add(file: file, project: project, projectPath: projectPath, sourceRoot: sourceRoot, target: target) } try project.write(path: projectPath) } catch { logger.logFatal("❌ ", item: "Unable to add files to Xcode project '\(projectPath)', error: '\(error.localizedDescription)'") } } - + /// Change Xcode project's base configuration. /// - Parameters: /// - fileReference: File reference of the `.xcconfig` file @@ -139,21 +133,21 @@ struct XcodeProjFactory { func changeBaseConfig(_ fileReference: PBXFileReference, in xcodeProject: XcodeProj, path: Path, - target: NamedTarget, + target: iOSTarget, autoSave: Bool = false) { do { for conf in xcodeProject.pbxproj.buildConfigurations { if let infoList = conf.buildSettings["INFOPLIST_FILE"] as? String, - infoList == target.value.source.info { + infoList == target.source.info { conf.baseConfiguration = fileReference } } if autoSave { try xcodeProject.write(path: path) } - logger.logInfo("✅ ", item: "Changed baseConfiguration of target '\(target.key)'", + logger.logInfo("✅ ", item: "Changed baseConfiguration of target '\(target.name)'", color: .green) } catch { - logger.logFatal("❌ ", item: "Unable to edit baseConfiguration for target '\(target.key)'") + logger.logFatal("❌ ", item: "Unable to edit baseConfiguration for target '\(target.name)'") } } @@ -161,25 +155,27 @@ struct XcodeProjFactory { /// - Parameters: /// - keyValue: Key/value pair to be modified /// - projectPath: Path to Xcode project - /// - target: iOSTarget on which the `buildSettings` should be changed. - /// - asTestSettings: If true, add configuraiton to test/non-host targets. + /// - targetName: Name of the target on which the `buildSettings` should be changed. /// - silent: Flag to determine if final logs are necessary func modify(_ keyValue: [String: String], in projectPath: Path, - target: iOSTarget, - asTestSettings: Bool = false, + targetName: String, + configurationTypes: [BuildConfigType] = BuildConfigType.allCases, silent: Bool = false) { do { let project = try XcodeProj(path: projectPath) + let configTypeNames = configurationTypes.map { $0.rawValue.lowercased() } logger.logInfo("Updating: ", item: projectPath) - - let matchingKey = asTestSettings ? target.testTarget : target.source.info + project.pbxproj.buildConfigurations - .filter({ ($0.buildSettings["INFOPLIST_FILE"] as? String)?.contains(matchingKey) ?? false }) + .filter({ ($0.buildSettings["INFOPLIST_FILE"] as? String)?.contains(targetName) ?? false }) + .filter({ configTypeNames.contains($0.name.lowercased()) }) .forEach { conf in + logger.logDebug( + "Build configuration type: ", item: conf.name, indentationLevel: 1, color: .blue) keyValue.forEach { (key, value) in - Logger.shared.logDebug("Item: ", item: "\(key) = \(value)", - indentationLevel: 1, color: .purple) + logger.logDebug( + "Item: ", item: "\(key) = \(value)", indentationLevel: 2, color: .purple) conf.buildSettings[key] = value } } @@ -195,59 +191,55 @@ struct XcodeProjFactory { } private extension XcodeProjFactory { - - private func createVarientsGroup( + private func getOrCreateVariantsGroup( for project: XcodeProj, path: Path, - sourceRoot: Path, - target: NamedTarget + target: iOSTarget ) throws -> PBXGroup? { - let variantsGroupPath = Path("\(path)/Variants") - let rootGroup = project.pbxproj.groups.first(where: { $0.path == sourceRoot.lastComponent }) - try rootGroup?.addGroup(named: variantsGroupPath.lastComponent) - let variantsGroup = rootGroup?.group(named: variantsGroupPath.lastComponent) - return variantsGroup + let groupName = "Variants" + let currentVariantsGroup = project.pbxproj.groups.first(where: { $0.path == groupName || $0.name == groupName }) + + guard currentVariantsGroup == nil else { return currentVariantsGroup } + let sourceGroup = project.pbxproj.groups.first(where: { $0.path == target.name }) + return try sourceGroup?.addGroup(named: groupName).first } - - // swiftlint:disable function_parameter_count + private func add( file: Path, - to project: XcodeProj, - path: Path, - variantsGroup: PBXGroup?, + project: XcodeProj, + projectPath: Path, sourceRoot: Path, - target: NamedTarget + target: iOSTarget ) throws { - guard let pbxTarget = project.pbxproj.targets(named: target.key).first + guard let variantsGroup = try getOrCreateVariantsGroup(for: project, path: projectPath, target: target) else { - logger.logFatal("❌ ", item: "Could not add files to Xcode project - Target '\(target.key)' not found.") - return + return logger.logFatal("❌ ", item: "Failed to generate Variants group at provided target name") } - - let fileRef = try variantsGroup?.addFile( + guard let pbxTarget = project.pbxproj.targets(named: target.name).first + else { + return logger.logFatal("❌ ", item: "Could not add files to Xcode project - Target '\(target.name)' not found.") + } + + let fileReference = try variantsGroup.addFile( at: file, sourceTree: .group, sourceRoot: sourceRoot, validatePresence: true ) - - let fileElement = PBXFileElement( - sourceTree: .group, - path: file.description, - name: file.lastComponent - ) - let buildFile = PBXBuildFile(file: fileElement) - let sourceBuildPhase = try pbxTarget.sourcesBuildPhase() - sourceBuildPhase?.files?.append(buildFile) - - /* - * If .xcconfig, set baseConfigurationReference to it - */ - if file.extension == "xcconfig", let fileReference = fileRef { - changeBaseConfig(fileReference, in: project, path: path, - target: target, autoSave: true) + + switch file.extension { + // .swift files must be added to the compile sources build phase + case "swift": + let sourcesBuildPhase = try? pbxTarget.sourcesBuildPhase() + _ = try sourcesBuildPhase?.add(file: fileReference) + + // .xcconfig is set to the project's base config + case "xcconfig": + changeBaseConfig(fileReference, in: project, path: projectPath, target: target, autoSave: true) + + // Unsupported file extension + default: + break } } - // swiftlint:enable function_parameter_count } -// swiftlint:enable file_length diff --git a/Sources/VariantsCore/Helpers/Bash.swift b/Sources/VariantsCore/Helpers/Bash.swift index 4ddd09fe..726c4142 100644 --- a/Sources/VariantsCore/Helpers/Bash.swift +++ b/Sources/VariantsCore/Helpers/Bash.swift @@ -11,6 +11,11 @@ struct Bash { var command: String var arguments: [String] + enum Stream { + case stdout + case stderr + } + init(_ command: String, arguments: String...) { self.command = command self.arguments = arguments @@ -20,12 +25,12 @@ struct Bash { _ = try capture() } - func capture() throws -> String? { + func capture(stream: Stream = .stdout) throws -> String? { guard var bashCommand = try execute(command: "/bin/bash", arguments: ["-l", "-c", "which \(command)"]) else { throw RuntimeError("\(command) not found") } bashCommand = bashCommand.trimmingCharacters(in: NSCharacterSet.whitespacesAndNewlines) - if let output = try execute(command: bashCommand, arguments: arguments) { + if let output = try execute(command: bashCommand, arguments: arguments, stream: stream) { // `dropLast()` is required as the output always contains a new line (`\n`) at the end. return String(output.dropLast()) } @@ -34,11 +39,13 @@ struct Bash { // MARK: - Private - private func execute(command: String, arguments: [String] = []) throws -> String? { + private func execute(command: String, arguments: [String] = [], stream: Stream = .stdout) throws -> String? { let process = Process() - let pipe = Pipe() + let stdoutPipe = Pipe() + let stderrPipe = Pipe() process.arguments = arguments - process.standardOutput = pipe + process.standardOutput = stdoutPipe + process.standardError = stderrPipe if #available(OSX 10.13, *) { process.executableURL = URL(fileURLWithPath: command) @@ -48,8 +55,15 @@ struct Bash { process.launch() } - let data = pipe.fileHandleForReading.readDataToEndOfFile() - let output = String(data: data, encoding: .utf8) - return output + switch stream { + case .stdout: + let stdoutData = stdoutPipe.fileHandleForReading.readDataToEndOfFile() + let stdout = String(data: stdoutData, encoding: .utf8) + return stdout + case .stderr: + let stderrData = stderrPipe.fileHandleForReading.readDataToEndOfFile() + let stderr = String(data: stderrData, encoding: .utf8) + return stderr + } } } diff --git a/Sources/VariantsCore/Helpers/SpecHelper.swift b/Sources/VariantsCore/Helpers/SpecHelper.swift index 80e9b642..dece3aa5 100644 --- a/Sources/VariantsCore/Helpers/SpecHelper.swift +++ b/Sources/VariantsCore/Helpers/SpecHelper.swift @@ -5,11 +5,11 @@ // Created by Arthur Alves // +// swiftlint:disable type_name + import Foundation import PathKit -// swiftlint:disable type_name - enum iOSProjectKey: String, CaseIterable { case project = "PROJECT" case target = "TARGET" diff --git a/Sources/VariantsCore/Schemas/Android/AndroidConfiguration.swift b/Sources/VariantsCore/Schemas/Android/AndroidConfiguration.swift index 25bce463..bf494593 100644 --- a/Sources/VariantsCore/Schemas/Android/AndroidConfiguration.swift +++ b/Sources/VariantsCore/Schemas/Android/AndroidConfiguration.swift @@ -6,6 +6,7 @@ // import Foundation +import ArgumentParser public struct AndroidConfiguration: Codable { let path: String @@ -14,6 +15,14 @@ public struct AndroidConfiguration: Codable { let variants: [AndroidVariant] let custom: [CustomProperty]? + var defaultVariant: AndroidVariant { + get throws { + guard let defaultVariant = variants.first(where: { $0.name.lowercased() == "default" }) + else { throw ValidationError("Variant 'default' not found.") } + return defaultVariant + } + } + enum CodingKeys: String, CodingKey { case path = "path" case appName = "app_name" diff --git a/Sources/VariantsCore/Schemas/Configuration.swift b/Sources/VariantsCore/Schemas/Configuration.swift index bd9c4f03..5ec60dd3 100644 --- a/Sources/VariantsCore/Schemas/Configuration.swift +++ b/Sources/VariantsCore/Schemas/Configuration.swift @@ -75,3 +75,19 @@ extension CustomProperty { } } } + +extension Sequence where Iterator.Element == CustomProperty { + var projectConfigurationValues: [CustomProperty] { + self.filter({ $0.destination == .project && !$0.isEnvironmentVariable }) + } + + var projectSecretConfigurationValues: [CustomProperty] { + self + .filter({ $0.destination == .project && $0.isEnvironmentVariable }) + .map { + CustomProperty(name: $0.name, + value: "os.environ.get('" + $0.environmentValue + "')", + destination: $0.destination) + } + } +} diff --git a/Sources/VariantsCore/Schemas/iOS/iOSConfiguration.swift b/Sources/VariantsCore/Schemas/iOS/iOSConfiguration.swift index 99f35a60..54442778 100644 --- a/Sources/VariantsCore/Schemas/iOS/iOSConfiguration.swift +++ b/Sources/VariantsCore/Schemas/iOS/iOSConfiguration.swift @@ -6,16 +6,17 @@ // import Foundation - -// swiftlint:disable type_name +import ArgumentParser internal extension CodingUserInfoKey { static let bundleID = CodingUserInfoKey(rawValue: "bundle_id")! } +// swiftlint:disable:next type_name public struct iOSConfiguration: Codable { let xcodeproj: String - let targets: [String: iOSTarget] + let target: iOSTarget + let extensions: [iOSExtension] let variants: [iOSVariant] let custom: [CustomProperty]? @@ -23,16 +24,27 @@ public struct iOSConfiguration: Codable { private let signing: iOSSigning? var pbxproj: String { - return xcodeproj+"/project.pbxproj" + return xcodeproj + "/project.pbxproj" } - + + var defaultVariant: iOSVariant { + get throws { + guard let defaultVariant = variants.first(where: { $0.name.lowercased() == "default" }) + else { throw ValidationError("Variant 'default' not found.") } + return defaultVariant + } + } + public init(from decoder: Decoder) throws { let container = try decoder.container(keyedBy: CodingKeys.self) self.xcodeproj = try container.decode(String.self, forKey: .xcodeproj) - self.targets = try container.decode([String: iOSTarget].self, forKey: .targets) - self.custom = try? container.decode([CustomProperty].self, forKey: .custom) - + self.target = try container.decode(iOSTarget.self, forKey: .target) + self.extensions = try container.decodeIfPresent([iOSExtension].self, forKey: .extensions) ?? [] + + let globalCustomProperties = try? container.decode([CustomProperty].self, forKey: .custom) + self.custom = globalCustomProperties + let globalPostSwitchScript = try container.decodeIfPresent(String.self, forKey: .postSwitchScript) let globalSigning = try container.decodeIfPresent(iOSSigning.self, forKey: .signing) let variantsDict = try container.decode([String: UnnamediOSVariant].self, forKey: .variants) @@ -40,8 +52,8 @@ public struct iOSConfiguration: Codable { self.postSwitchScript = globalPostSwitchScript self.signing = globalSigning self.variants = try variantsDict - .map { try iOSVariant(from: $1, name: $0, globalSigning: globalSigning, globalPostSwitchScript: globalPostSwitchScript) } + .map { + try iOSVariant(from: $1, name: $0, globalCustomProperties: globalCustomProperties, + globalSigning: globalSigning, globalPostSwitchScript: globalPostSwitchScript) } } } - -// swiftlint:enable type_name diff --git a/Sources/VariantsCore/Schemas/iOS/iOSExtension.swift b/Sources/VariantsCore/Schemas/iOS/iOSExtension.swift new file mode 100644 index 00000000..4105df5a --- /dev/null +++ b/Sources/VariantsCore/Schemas/iOS/iOSExtension.swift @@ -0,0 +1,81 @@ +// +// Variants +// +// Copyright (c) Backbase B.V. - https://www.backbase.com +// Created by Gabriel Rodrigues Minucci on 24/01/2025. +// + +import Foundation + +// swiftlint:disable:next type_name +public struct iOSExtension: Codable { + let name: String + let bundleNamingOption: BundleNamingOption + let signed: Bool + + enum CodingKeys: String, CodingKey { + case name + case bundleID = "bundle_id" + case bundleSuffix = "bundle_suffix" + case signed + } + + enum BundleNamingOption: Codable, Equatable { + case explicit(String) + case suffix(String) + + static func == (lhs: Self, rhs: Self) -> Bool { + switch (lhs, rhs) { + case let (.explicit(lhsValue), .explicit(rhsValue)): + return lhsValue == rhsValue + case let (.suffix(lhsValue), .suffix(rhsValue)): + return lhsValue == rhsValue + default: + return false + } + } + } + + public init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: CodingKeys.self) + name = try container.decode(String.self, forKey: .name) + signed = try container.decode(Bool.self, forKey: .signed) + + let bundleID = try container.decodeIfPresent(String.self, forKey: .bundleID) + let bundleSuffix = try container.decodeIfPresent(String.self, forKey: .bundleSuffix) + + if let bundleID, bundleSuffix == nil { + bundleNamingOption = .explicit(bundleID) + } else if let bundleSuffix, bundleID == nil { + bundleNamingOption = .suffix(bundleSuffix) + } else { + throw RuntimeError( + """ + Target extension "\(name)" have "bundle_suffix" and "bundle_id" configured at the same time or no \ + configuration were provided to any of them. Please provide only one of them per target extension. + """) + } + } + + public func encode(to encoder: any Encoder) throws { + var container = encoder.container(keyedBy: CodingKeys.self) + try container.encode(name, forKey: .name) + try container.encode(signed, forKey: .signed) + + switch bundleNamingOption { + case .explicit(let bundleID): + try container.encode(bundleID, forKey: .bundleID) + case .suffix(let bundleSuffix): + try container.encode(bundleSuffix, forKey: .bundleSuffix) + } + } + + func makeBundleID(variant: iOSVariant, target: iOSTarget) -> String { + switch bundleNamingOption { + case .explicit(let bundleID): + return bundleID + case .suffix(let bundleSuffix): + return variant.makeBundleID(for: target).appending(".\(bundleSuffix)") + } + } +} diff --git a/Sources/VariantsCore/Schemas/iOS/iOSSigning.swift b/Sources/VariantsCore/Schemas/iOS/iOSSigning.swift index a11d516d..9d9c141c 100644 --- a/Sources/VariantsCore/Schemas/iOS/iOSSigning.swift +++ b/Sources/VariantsCore/Schemas/iOS/iOSSigning.swift @@ -7,24 +7,56 @@ import Foundation -// swiftlint:disable type_name - -struct iOSSigning: Codable { +// swiftlint:disable:next type_name +struct iOSSigning: Codable, Equatable { let teamName: String? let teamID: String? - let exportMethod: Type? + let exportMethod: ExportMethod? let matchURL: String? + let style: SigningStyle + let autoDetectSigningIdentity: Bool + var codeSigningIdentity: String? { + fetchSigningCertificate() + } + enum CodingKeys: String, CodingKey { case teamName = "team_name" case teamID = "team_id" case exportMethod = "export_method" case matchURL = "match_url" + case style + case autoDetectSigningIdentity = "auto_detect_signing_identity" + } + + init(from decoder: any Decoder) throws { + let container = try decoder.container(keyedBy: CodingKeys.self) + self.teamName = try container.decodeIfPresent(String.self, forKey: .teamName) + self.teamID = try container.decodeIfPresent(String.self, forKey: .teamID) + self.exportMethod = try container.decodeIfPresent(ExportMethod.self, forKey: .exportMethod) + self.matchURL = try container.decodeIfPresent(String.self, forKey: .matchURL) + self.style = try container.decodeIfPresent(iOSSigning.SigningStyle.self, forKey: .style) ?? .manual + let signingIdentity = try container.decodeIfPresent(Bool.self, forKey: .autoDetectSigningIdentity) + self.autoDetectSigningIdentity = signingIdentity ?? true + } + + init(teamName: String?, + teamID: String?, + exportMethod: ExportMethod?, + matchURL: String?, + style: SigningStyle, + autoDetectSigningIdentity: Bool) { + self.teamName = teamName + self.teamID = teamID + self.exportMethod = exportMethod + self.matchURL = matchURL + self.style = style + self.autoDetectSigningIdentity = autoDetectSigningIdentity } } extension iOSSigning { - enum `Type`: String, Codable { + enum ExportMethod: String, Codable { case appstore case development case adhoc @@ -42,6 +74,19 @@ extension iOSSigning { return "match InHouse" } } + + var isDistribution: Bool { + self == .appstore || self == .enterprise + } + + var certType: String { + isDistribution ? "Distribution" : "Development" + } + } + + enum SigningStyle: String, Codable { + case automatic + case manual } } @@ -63,7 +108,7 @@ extension iOSSigning { for property in mirroredObject.children { if let label = property.label { let stringValue = property.value as? String - let typeValue = (property.value as? Type)?.rawValue + let typeValue = (property.value as? ExportMethod)?.rawValue if let value = stringValue ?? typeValue { customProperties.append(CustomProperty(name: label.uppercased(), value: value, @@ -78,19 +123,48 @@ extension iOSSigning { infix operator ~: AdditionPrecedence extension iOSSigning { static func ~ (lhs: iOSSigning, rhs: iOSSigning?) throws -> iOSSigning { - let signing = iOSSigning(teamName: lhs.teamName ?? rhs?.teamName, - teamID: lhs.teamID ?? rhs?.teamID, - exportMethod: lhs.exportMethod ?? rhs?.exportMethod, - matchURL: lhs.matchURL ?? rhs?.matchURL) - if signing.teamName == nil { - throw iOSSigning.missingParameterError(CodingKeys.teamName) - } else if signing.teamID == nil { - throw iOSSigning.missingParameterError(CodingKeys.teamID) - } else if signing.exportMethod == nil { - throw iOSSigning.missingParameterError(CodingKeys.exportMethod) - } + let signing = iOSSigning( + teamName: lhs.teamName ?? rhs?.teamName, + teamID: lhs.teamID ?? rhs?.teamID, + exportMethod: lhs.exportMethod ?? rhs?.exportMethod, + matchURL: lhs.matchURL ?? rhs?.matchURL, + style: lhs.style, + autoDetectSigningIdentity: lhs.autoDetectSigningIdentity) + + guard signing.teamName != nil else { throw iOSSigning.missingParameterError(CodingKeys.teamName) } + guard signing.teamID != nil else { throw iOSSigning.missingParameterError(CodingKeys.teamID) } + guard signing.exportMethod != nil else { throw iOSSigning.missingParameterError(CodingKeys.exportMethod) } + return signing } } -// swiftlint:enable type_name +extension iOSSigning { + private func fetchSigningCertificate() -> String? { + guard let teamID else { return nil } + + do { + let output = try Bash("security", arguments: "find-identity", "-v", "-p", "codesigning") + .capture() + + guard let output else { return nil } + let lines = output.split(separator: "\n") + + let matches = lines.compactMap { line -> String? in + guard line.contains(teamID) else { return nil } + + if let teamName, !line.contains(teamName) { return nil } + if let certType = exportMethod?.certType.lowercased(), + !line.contains(certType) { return nil } + + let components = line.split(separator: "\"", maxSplits: 2, omittingEmptySubsequences: false) + guard components.count > 1 else { return nil } + + return String(components[1]) + } + return matches.first + } catch { + return nil + } + } +} diff --git a/Sources/VariantsCore/Schemas/iOS/iOSTarget.swift b/Sources/VariantsCore/Schemas/iOS/iOSTarget.swift index 2e1bd843..b1e9ff1e 100644 --- a/Sources/VariantsCore/Schemas/iOS/iOSTarget.swift +++ b/Sources/VariantsCore/Schemas/iOS/iOSTarget.swift @@ -7,10 +7,7 @@ import Foundation -// swiftlint:disable type_name - -public typealias NamedTarget = (key: String, value: iOSTarget) - +// swiftlint:disable:next type_name public struct iOSTarget: Codable { let name: String let app_icon: String @@ -27,10 +24,9 @@ public struct iOSTarget: Codable { } } +// swiftlint:disable:next type_name public struct iOSSource: Codable { let path: String let info: String let config: String } - -// swiftlint:enable type_name diff --git a/Sources/VariantsCore/Schemas/iOS/iOSVariant.swift b/Sources/VariantsCore/Schemas/iOS/iOSVariant.swift index 1bf1d1c4..636bf0cb 100644 --- a/Sources/VariantsCore/Schemas/iOS/iOSVariant.swift +++ b/Sources/VariantsCore/Schemas/iOS/iOSVariant.swift @@ -7,8 +7,7 @@ import Foundation -// swiftlint:disable type_name - +// swiftlint:disable:next type_name public struct iOSVariant: Variant { let name: String let versionName: String @@ -16,12 +15,13 @@ public struct iOSVariant: Variant { let appIcon: String? let appName: String? let storeDestination: Destination - let signing: iOSSigning? + let debugSigning: iOSSigning? + let releaseSigning: iOSSigning? let custom: [CustomProperty]? let postSwitchScript: String? private let bundleNamingOption: BundleNamingOption - + public var title: String { name } var configName: String { @@ -38,8 +38,10 @@ public struct iOSVariant: Variant { } init( - name: String, versionName: String, versionNumber: Int, appIcon: String?, appName: String?, storeDestination: String?, - custom: [CustomProperty]?, idSuffix: String?, bundleID: String?, variantSigning: iOSSigning?, globalSigning: iOSSigning?, + name: String, versionName: String, versionNumber: Int, appIcon: String?, appName: String?, + storeDestination: String?, idSuffix: String?, bundleID: String?, + globalCustomProperties: [CustomProperty]?, variantCustomProperties: [CustomProperty]?, + globalSigning: iOSSigning?, debugSigning: iOSSigning?, releaseSigning: iOSSigning?, globalPostSwitchScript: String?, variantPostSwitchScript: String?) throws { self.name = name @@ -48,8 +50,9 @@ public struct iOSVariant: Variant { self.appIcon = appIcon self.appName = appName self.storeDestination = try Self.parseDestination(name: name, destination: storeDestination) ?? .appStore - self.signing = try Self.parseSigning(name: name, variantSigning: variantSigning, globalSigning: globalSigning) - self.custom = custom + self.debugSigning = try Self.parseSigning(name: name, override: debugSigning, base: globalSigning) + self.releaseSigning = try Self.parseSigning(name: name, override: releaseSigning, base: globalSigning) + self.custom = Self.parseCustomProperties(variantCustom: variantCustomProperties, globalCustom: globalCustomProperties) self.bundleNamingOption = try Self.parseBundleConfiguration(name: name, idSuffix: idSuffix, bundleID: bundleID) self.postSwitchScript = Self.parsePostSwitchScript(globalScript: globalPostSwitchScript, variantScript: variantPostSwitchScript) @@ -66,6 +69,7 @@ public struct iOSVariant: Variant { } } + // TODO: is debug? func getDefaultValues(for target: iOSTarget) -> [(key: String, value: String)] { var customDictionary: [String: String] = [ "V_APP_NAME": appName ?? target.name + configName, @@ -75,17 +79,14 @@ public struct iOSVariant: Variant { "V_APP_ICON": appIcon ?? target.app_icon ] - if signing?.matchURL != nil, let exportMethod = signing?.exportMethod { + if releaseSigning?.matchURL != nil, let exportMethod = releaseSigning?.exportMethod { customDictionary["V_MATCH_PROFILE"] = "\(exportMethod.prefix) \(makeBundleID(for: target))" } - - custom? - .filter { $0.destination == .project && !$0.isEnvironmentVariable } - .forEach { customDictionary[$0.name] = $0.value } - + (custom?.projectConfigurationValues ?? []).forEach { customDictionary[$0.name] = $0.value } + return customDictionary.sorted(by: {$0.key < $1.key}) } - + private static func parseDestination(name: String, destination: String?) throws -> Destination? { guard let destinationString = destination else { return nil } @@ -100,23 +101,28 @@ public struct iOSVariant: Variant { return destination } - private static func parseSigning(name: String, variantSigning: iOSSigning?, globalSigning: iOSSigning?) throws -> iOSSigning? { - if let variantSigning = variantSigning, let globalSigning = globalSigning { - return try variantSigning ~ globalSigning - } else if let variantSigning = variantSigning { - return try variantSigning ~ nil - } else if let globalSigning = globalSigning { - return try globalSigning ~ nil + private static func parseSigning(name: String, override: iOSSigning?, base: iOSSigning?) throws -> iOSSigning? { + if let override, let base { + return try override ~ base + } else if let override { + return try override ~ nil + } else if let base { + return try base ~ nil } else { - Logger.shared.logWarning(item: + throw RuntimeError( """ Variant "\(name)" doesn't contain a 'signing' configuration. \ Create a global 'signing' configuration or make sure all variants have this property. """) - return nil } } - + + private static func parseCustomProperties(variantCustom: [CustomProperty]?, globalCustom: [CustomProperty]?) -> [CustomProperty] { + let variantCustomProperties = variantCustom ?? [] + let globalMinusOverrideProperties = (globalCustom ?? []).filter { !variantCustomProperties.contains($0) } + return globalMinusOverrideProperties + variantCustomProperties + } + private static func parsePostSwitchScript(globalScript: String?, variantScript: String?) -> String? { if let globalScript = globalScript, let variantScript = variantScript { return "\(globalScript) && \(variantScript)" @@ -172,6 +178,8 @@ struct UnnamediOSVariant: Codable { let idSuffix: String? let bundleID: String? let signing: iOSSigning? + let debugSigning: iOSSigning? + let releaseSigning: iOSSigning? let custom: [CustomProperty]? let storeDestination: String? let postSwitchScript: String? @@ -184,6 +192,8 @@ struct UnnamediOSVariant: Codable { case idSuffix = "id_suffix" case bundleID = "bundle_id" case signing + case releaseSigning = "release_signing" + case debugSigning = "debug_signing" case custom case storeDestination = "store_destination" case postSwitchScript @@ -200,6 +210,8 @@ extension UnnamediOSVariant { idSuffix = try values.decodeIfPresentOrReadFromEnv(String.self, forKey: .idSuffix) bundleID = try values.decodeIfPresentOrReadFromEnv(String.self, forKey: .bundleID) signing = try values.decodeIfPresent(iOSSigning.self, forKey: .signing) + debugSigning = try values.decodeIfPresent(iOSSigning.self, forKey: .debugSigning) + releaseSigning = try values.decodeIfPresent(iOSSigning.self, forKey: .releaseSigning) custom = try values.decodeIfPresent([CustomProperty].self, forKey: .custom) storeDestination = try values.decodeIfPresentOrReadFromEnv(String.self, forKey: .storeDestination) postSwitchScript = try values.decodeIfPresent(String.self, forKey: .postSwitchScript) @@ -207,7 +219,9 @@ extension UnnamediOSVariant { } extension iOSVariant { - init(from unnamediOSVariant: UnnamediOSVariant, name: String, globalSigning: iOSSigning?, globalPostSwitchScript: String?) throws { + init(from unnamediOSVariant: UnnamediOSVariant, name: String, globalCustomProperties: [CustomProperty]?, + globalSigning: iOSSigning?, globalPostSwitchScript: String?) + throws { try self.init( name: name, versionName: unnamediOSVariant.versionName, @@ -215,14 +229,14 @@ extension iOSVariant { appIcon: unnamediOSVariant.appIcon, appName: unnamediOSVariant.appName, storeDestination: unnamediOSVariant.storeDestination, - custom: unnamediOSVariant.custom, idSuffix: unnamediOSVariant.idSuffix, bundleID: unnamediOSVariant.bundleID, - variantSigning: unnamediOSVariant.signing, + globalCustomProperties: globalCustomProperties, + variantCustomProperties: unnamediOSVariant.custom, globalSigning: globalSigning, + debugSigning: unnamediOSVariant.debugSigning ?? unnamediOSVariant.signing, + releaseSigning: unnamediOSVariant.releaseSigning ?? unnamediOSVariant.signing, globalPostSwitchScript: globalPostSwitchScript, variantPostSwitchScript: unnamediOSVariant.postSwitchScript) } } - -// swiftlint:enable type_name diff --git a/Templates/ios/Variants.swift.template.gyb b/Templates/ios/Variants.swift.template.gyb index c7d01afc..eac6c31a 100644 --- a/Templates/ios/Variants.swift.template.gyb +++ b/Templates/ios/Variants.swift.template.gyb @@ -8,7 +8,6 @@ def encode(string, cipher): bytes = string.encode("UTF-8") return [ord(bytes[i]) ^ cipher[i % len(cipher)] for i in range(0, len(bytes))] }% - // // Variants // @@ -28,30 +27,27 @@ public struct Variants { {% if configurationValues %} // MARK: - ConfigurationValueKey /// Custom configuration values coming from variants.yml as enum cases - + public enum ConfigurationValueKey: String { {% for confValue in configurationValues %} - case {{ confValue.name }} {% endfor %} + case {{ confValue.name }}{% endfor %} } - + static func configurationValue(for key: ConfigurationValueKey) -> Any? { return Self.configuration[key.rawValue] } - {% endif %} - - {% if secrets %} + {% endif %}{% if secrets %} // MARK: - Secrets /// Encrypted secrets coming from variants.yml as environment variables - + public struct Secrets { - + private static let salt: [UInt8] = [ %{ salt = [ord(byte) for byte in os.urandom(64)] }% % for chunk in chunks(salt, 8): ${"".join(["0x%02x, " % byte for byte in chunk])} % end ] - - {% for secret in secrets %} + {% for secret in secrets %} static var {{ secret.name }}: String { let encoded: [UInt8] = [ % for chunk in chunks(encode({{ secret.value }}, salt), 8): @@ -61,13 +57,11 @@ public struct Variants { return decode(encoded, cipher: salt) } - {% endfor %} - + {% endfor %} private static func decode(_ encoded: [UInt8], cipher: [UInt8]) -> String { String(decoding: encoded.enumerated().map { (offset, element) in element ^ cipher[offset % cipher.count] }, as: UTF8.self) } - } - {% endif %} + }{% endif %} } diff --git a/Templates/ios/_fastlane/Gemfile b/Templates/ios/_fastlane/Gemfile index 96c7cc57..bdc6074d 100644 --- a/Templates/ios/_fastlane/Gemfile +++ b/Templates/ios/_fastlane/Gemfile @@ -1,11 +1,10 @@ source 'https://rubygems.org' ​ -gem 'cocoapods', '1.11.3' +gem 'cocoapods' gem 'cocoapods-art' gem 'fastlane' gem 'slather' -gem 'json', '2.3.0' -gem 'nokogiri', '1.13.5' +gem 'nokogiri' ​ plugins_path = File.join(File.dirname(__FILE__), 'fastlane', 'Pluginfile') eval_gemfile(plugins_path) if File.exist?(plugins_path) diff --git a/Templates/ios/_fastlane/Gemfile.lock b/Templates/ios/_fastlane/Gemfile.lock deleted file mode 100644 index 1498b1fa..00000000 --- a/Templates/ios/_fastlane/Gemfile.lock +++ /dev/null @@ -1,317 +0,0 @@ -GEM - remote: https://rubygems.org/ - specs: - CFPropertyList (3.0.6) - rexml - activesupport (6.1.7.2) - concurrent-ruby (~> 1.0, >= 1.0.2) - i18n (>= 1.6, < 2) - minitest (>= 5.1) - tzinfo (~> 2.0) - zeitwerk (~> 2.3) - addressable (2.8.1) - public_suffix (>= 2.0.2, < 6.0) - algoliasearch (1.27.5) - httpclient (~> 2.8, >= 2.8.3) - json (>= 1.5.1) - artifactory (3.0.15) - atomos (0.1.3) - aws-eventstream (1.2.0) - aws-partitions (1.711.0) - aws-sdk-core (3.170.0) - aws-eventstream (~> 1, >= 1.0.2) - aws-partitions (~> 1, >= 1.651.0) - aws-sigv4 (~> 1.5) - jmespath (~> 1, >= 1.6.1) - aws-sdk-kms (1.62.0) - aws-sdk-core (~> 3, >= 3.165.0) - aws-sigv4 (~> 1.1) - aws-sdk-s3 (1.119.1) - aws-sdk-core (~> 3, >= 3.165.0) - aws-sdk-kms (~> 1) - aws-sigv4 (~> 1.4) - aws-sigv4 (1.5.2) - aws-eventstream (~> 1, >= 1.0.2) - babosa (1.0.4) - claide (1.1.0) - clamp (1.3.2) - cocoapods (1.11.3) - addressable (~> 2.8) - claide (>= 1.0.2, < 2.0) - cocoapods-core (= 1.11.3) - cocoapods-deintegrate (>= 1.0.3, < 2.0) - cocoapods-downloader (>= 1.4.0, < 2.0) - cocoapods-plugins (>= 1.0.0, < 2.0) - cocoapods-search (>= 1.0.0, < 2.0) - cocoapods-trunk (>= 1.4.0, < 2.0) - cocoapods-try (>= 1.1.0, < 2.0) - colored2 (~> 3.1) - escape (~> 0.0.4) - fourflusher (>= 2.3.0, < 3.0) - gh_inspector (~> 1.0) - molinillo (~> 0.8.0) - nap (~> 1.0) - ruby-macho (>= 1.0, < 3.0) - xcodeproj (>= 1.21.0, < 2.0) - cocoapods-art (1.1.0) - cocoapods-core (1.11.3) - activesupport (>= 5.0, < 7) - addressable (~> 2.8) - algoliasearch (~> 1.0) - concurrent-ruby (~> 1.1) - fuzzy_match (~> 2.0.4) - nap (~> 1.0) - netrc (~> 0.11) - public_suffix (~> 4.0) - typhoeus (~> 1.0) - cocoapods-deintegrate (1.0.5) - cocoapods-downloader (1.6.3) - cocoapods-plugins (1.0.0) - nap - cocoapods-search (1.0.1) - cocoapods-trunk (1.6.0) - nap (>= 0.8, < 2.0) - netrc (~> 0.11) - cocoapods-try (1.2.0) - coderay (1.1.3) - colored (1.2) - colored2 (3.1.2) - commander (4.6.0) - highline (~> 2.0.0) - concurrent-ruby (1.2.0) - declarative (0.0.20) - digest-crc (0.6.4) - rake (>= 12.0.0, < 14.0.0) - domain_name (0.5.20190701) - unf (>= 0.0.5, < 1.0.0) - dotenv (2.8.1) - emoji_regex (3.2.3) - escape (0.0.4) - ethon (0.16.0) - ffi (>= 1.15.0) - excon (0.99.0) - faraday (1.10.3) - faraday-em_http (~> 1.0) - faraday-em_synchrony (~> 1.0) - faraday-excon (~> 1.1) - faraday-httpclient (~> 1.0) - faraday-multipart (~> 1.0) - faraday-net_http (~> 1.0) - faraday-net_http_persistent (~> 1.0) - faraday-patron (~> 1.0) - faraday-rack (~> 1.0) - faraday-retry (~> 1.0) - ruby2_keywords (>= 0.0.4) - faraday-cookie_jar (0.0.7) - faraday (>= 0.8.0) - http-cookie (~> 1.0.0) - faraday-em_http (1.0.0) - faraday-em_synchrony (1.0.0) - faraday-excon (1.1.0) - faraday-httpclient (1.0.1) - faraday-multipart (1.0.4) - multipart-post (~> 2) - faraday-net_http (1.0.1) - faraday-net_http_persistent (1.2.0) - faraday-patron (1.0.0) - faraday-rack (1.0.0) - faraday-retry (1.0.3) - faraday_middleware (1.2.0) - faraday (~> 1.0) - fastimage (2.2.6) - fastlane (2.211.0) - CFPropertyList (>= 2.3, < 4.0.0) - addressable (>= 2.8, < 3.0.0) - artifactory (~> 3.0) - aws-sdk-s3 (~> 1.0) - babosa (>= 1.0.3, < 2.0.0) - bundler (>= 1.12.0, < 3.0.0) - colored - commander (~> 4.6) - dotenv (>= 2.1.1, < 3.0.0) - emoji_regex (>= 0.1, < 4.0) - excon (>= 0.71.0, < 1.0.0) - faraday (~> 1.0) - faraday-cookie_jar (~> 0.0.6) - faraday_middleware (~> 1.0) - fastimage (>= 2.1.0, < 3.0.0) - gh_inspector (>= 1.1.2, < 2.0.0) - google-apis-androidpublisher_v3 (~> 0.3) - google-apis-playcustomapp_v1 (~> 0.1) - google-cloud-storage (~> 1.31) - highline (~> 2.0) - json (< 3.0.0) - jwt (>= 2.1.0, < 3) - mini_magick (>= 4.9.4, < 5.0.0) - multipart-post (~> 2.0.0) - naturally (~> 2.2) - optparse (~> 0.1.1) - plist (>= 3.1.0, < 4.0.0) - rubyzip (>= 2.0.0, < 3.0.0) - security (= 0.1.3) - simctl (~> 1.6.3) - terminal-notifier (>= 2.0.0, < 3.0.0) - terminal-table (>= 1.4.5, < 2.0.0) - tty-screen (>= 0.6.3, < 1.0.0) - tty-spinner (>= 0.8.0, < 1.0.0) - word_wrap (~> 1.0.0) - xcodeproj (>= 1.13.0, < 2.0.0) - xcpretty (~> 0.3.0) - xcpretty-travis-formatter (>= 0.0.3) - fastlane-plugin-appcenter (2.0.0) - fastlane-plugin-lizard (1.3.3) - bundler - fastlane - pry - fastlane-plugin-xcconfig (2.0.0) - fastlane-plugin-xchtmlreport (0.1.1) - ffi (1.15.5) - fourflusher (2.3.1) - fuzzy_match (2.0.4) - gh_inspector (1.1.3) - google-apis-androidpublisher_v3 (0.34.0) - google-apis-core (>= 0.9.1, < 2.a) - google-apis-core (0.11.0) - addressable (~> 2.5, >= 2.5.1) - googleauth (>= 0.16.2, < 2.a) - httpclient (>= 2.8.1, < 3.a) - mini_mime (~> 1.0) - representable (~> 3.0) - retriable (>= 2.0, < 4.a) - rexml - webrick - google-apis-iamcredentials_v1 (0.16.0) - google-apis-core (>= 0.9.1, < 2.a) - google-apis-playcustomapp_v1 (0.12.0) - google-apis-core (>= 0.9.1, < 2.a) - google-apis-storage_v1 (0.19.0) - google-apis-core (>= 0.9.0, < 2.a) - google-cloud-core (1.6.0) - google-cloud-env (~> 1.0) - google-cloud-errors (~> 1.0) - google-cloud-env (1.6.0) - faraday (>= 0.17.3, < 3.0) - google-cloud-errors (1.3.0) - google-cloud-storage (1.44.0) - addressable (~> 2.8) - digest-crc (~> 0.4) - google-apis-iamcredentials_v1 (~> 0.1) - google-apis-storage_v1 (~> 0.19.0) - google-cloud-core (~> 1.6) - googleauth (>= 0.16.2, < 2.a) - mini_mime (~> 1.0) - googleauth (1.3.0) - faraday (>= 0.17.3, < 3.a) - jwt (>= 1.4, < 3.0) - memoist (~> 0.16) - multi_json (~> 1.11) - os (>= 0.9, < 2.0) - signet (>= 0.16, < 2.a) - highline (2.0.3) - http-cookie (1.0.5) - domain_name (~> 0.5) - httpclient (2.8.3) - i18n (1.12.0) - concurrent-ruby (~> 1.0) - jmespath (1.6.2) - json (2.3.0) - jwt (2.7.0) - memoist (0.16.2) - method_source (1.0.0) - mini_magick (4.12.0) - mini_mime (1.1.2) - mini_portile2 (2.8.1) - minitest (5.17.0) - molinillo (0.8.0) - multi_json (1.15.0) - multipart-post (2.0.0) - nanaimo (0.3.0) - nap (1.1.0) - naturally (2.2.1) - netrc (0.11.0) - nokogiri (1.13.5) - mini_portile2 (~> 2.8.0) - racc (~> 1.4) - optparse (0.1.1) - os (1.1.4) - plist (3.6.0) - pry (0.14.2) - coderay (~> 1.1) - method_source (~> 1.0) - public_suffix (4.0.7) - racc (1.6.2) - rake (13.0.6) - representable (3.2.0) - declarative (< 0.1.0) - trailblazer-option (>= 0.1.1, < 0.2.0) - uber (< 0.2.0) - retriable (3.1.2) - rexml (3.2.5) - rouge (2.0.7) - ruby-macho (2.5.1) - ruby2_keywords (0.0.5) - rubyzip (2.3.2) - security (0.1.3) - signet (0.17.0) - addressable (~> 2.8) - faraday (>= 0.17.5, < 3.a) - jwt (>= 1.5, < 3.0) - multi_json (~> 1.10) - simctl (1.6.10) - CFPropertyList - naturally - slather (2.7.2) - CFPropertyList (>= 2.2, < 4) - activesupport - clamp (~> 1.3) - nokogiri (~> 1.12) - xcodeproj (~> 1.21) - terminal-notifier (2.0.0) - terminal-table (1.8.0) - unicode-display_width (~> 1.1, >= 1.1.1) - trailblazer-option (0.1.2) - tty-cursor (0.7.1) - tty-screen (0.8.1) - tty-spinner (0.9.3) - tty-cursor (~> 0.7) - typhoeus (1.4.0) - ethon (>= 0.9.0) - tzinfo (2.0.6) - concurrent-ruby (~> 1.0) - uber (0.1.0) - unf (0.1.4) - unf_ext - unf_ext (0.0.8.2) - unicode-display_width (1.8.0) - webrick (1.8.1) - word_wrap (1.0.0) - xcodeproj (1.22.0) - CFPropertyList (>= 2.3.3, < 4.0) - atomos (~> 0.1.3) - claide (>= 1.0.2, < 2.0) - colored2 (~> 3.1) - nanaimo (~> 0.3.0) - rexml (~> 3.2.4) - xcpretty (0.3.0) - rouge (~> 2.0.7) - xcpretty-travis-formatter (1.0.1) - xcpretty (~> 0.2, >= 0.0.7) - zeitwerk (2.6.7) -​ -PLATFORMS - ruby -​ -DEPENDENCIES - cocoapods (= 1.11.3) - cocoapods-art - fastlane - fastlane-plugin-appcenter - fastlane-plugin-lizard - fastlane-plugin-xcconfig - fastlane-plugin-xchtmlreport - json (= 2.3.0) - nokogiri (= 1.13.5) - slather -​ -BUNDLED WITH - 2.1.4 diff --git a/Templates/ios/matchfile_template.rb b/Templates/ios/matchfile_template.rb index 83898992..42509e12 100644 --- a/Templates/ios/matchfile_template.rb +++ b/Templates/ios/matchfile_template.rb @@ -2,7 +2,7 @@ git_url("{{ git_url }}") {% else %} -# Sample: "git@github.com:backbase/match.git" + git_url(YOUR_MATCH_GIT_URL) {% endif %} @@ -10,12 +10,24 @@ storage_mode("git") {% if export_method %} -# appstore, development, adhoc, enterprise + type("{{ export_method }}") {% endif %} -{% if bundle_id %} -app_identifier("{{ bundle_id }}") +{% if app_identifiers %} + +{% if app_identifiers.count == 1 %} + +app_identifier("{{ app_identifiers[0] }}") + +{% else %} + +app_identifier([ +{% for identifier in app_identifiers %} + "{{ identifier }}"{% if not forloop.last %},{% endif %} +{% endfor %}]) + +{% endif %} {% endif %} diff --git a/Templates/ios/variants-template.yml b/Templates/ios/variants-template.yml index 63d402e3..3735d522 100644 --- a/Templates/ios/variants-template.yml +++ b/Templates/ios/variants-template.yml @@ -5,85 +5,99 @@ ios: xcodeproj: {{ PROJECT }}.xcodeproj - targets: - {{ TARGET }}: + target: name: {{ APP_NAME }} bundle_id: {{ APP_BUNDLE_ID }} test_target: {{ TEST_TARGET }} app_icon: {{ APP_ICON }} source: - path: {{ SOURCE }} - info: {{ INFO_PLIST }} - config: {{ SOURCE }} + path: {{ SOURCE }} + info: {{ INFO_PLIST }} + config: {{ SOURCE }} + # ---------------------------------------------------------------------- + # This can be used to add a list of target extensions to be included in + # the signing phase, both in the Matchfile and Xcode target + # + # Comment or delete section below if necessary. + # ---------------------------------------------------------------------- + #extensions: + # - name: MyAppExtension + # bundle_id: com.myApp.MyAppExtension + # signed: true variants: - # Default variant is mandatory, do not remove - default: - version_name: 0.0.1 - version_number: 1 - # - # 'store_destination' can be: AppStore, TestFlight or AppCenter - store_destination: AppStore - - # - # Same as `ios.signing`, but this will override those values. - signing: - # 'match_url' isn't mandatory, only if you use Match to sign your app - # match_url: "git@github.com:sample/match.git" - team_name: "iPhone Distribution" - team_id: "AB1234567D" - - # - # custom: - Not required. - # - # You can have as many custom fields as possible. - # Only strings allowed. - # - # The value of will be written to 1 of 2 possible destinations: - # - project => variants.xcconfig - # - fastlane => fastlane/parameters/variants_params.rb - # - custom: - - name: OTHER_SWIFT_FLAGS - value: $(inherited) - env: false - destination: project - - name: SAMPLE_FASTLANE_PROPERTY - value: This will be available to fastlane - env: false - destination: fastlane - # - # Sample variant, "beta". - # Only `version_name` and `version_number` are mandatory fields - # - BETA: - id_suffix: beta - # If app_icon isn't specified, the value fallbacks to target.app_icon - app_icon: AppIcon.beta - version_name: 0.0.1 - version_number: 1 - + # Default variant is mandatory, do not remove + default: + version_name: 0.0.1 + version_number: 1 + # + # 'store_destination' can be: AppStore, TestFlight or AppCenter + store_destination: AppStore + + # + # Same as `ios.signing`, but this will override those values. + signing: + # 'match_url' isn't mandatory, only if you use Match to sign your app + # match_url: "git@github.com:sample/match.git" + team_name: "iPhone Distribution" + team_id: "AB1234567D" + + # Should Variant try to auto detect signing identity + # if set to true, Variant will use the team_id, team_name and export_method + # to detect code signing identity from the Keychain Access + # default value is `true` + auto_detect_signing_identity: true + # + # custom: - Not required. + # + # You can have as many custom fields as possible. + # Only strings allowed. + # + # The value of will be written to 1 of 2 possible destinations: + # - project => variants.xcconfig + # - fastlane => fastlane/parameters/variants_params.rb + # + custom: + - name: OTHER_SWIFT_FLAGS + value: $(inherited) + env: false + destination: project + - name: SAMPLE_FASTLANE_PROPERTY + value: This will be available to fastlane + env: false + destination: fastlane # - # 'store_destination' can be: AppStore, TestFlight or AppCenter - store_destination: AppCenter - + # Sample variant, "beta". + # Only `version_name` and `version_number` are mandatory fields # - # Same as `ios.signing`, but this will override those values. - signing: - # 'match_url' isn't mandatory, only if you use Match to sign your app - # match_url: "git@github.com:sample/match.git" - team_name: "iPhone Distribution: Enterprise Sample" - team_id: "7A1234567D" - export_method: "enterprise" - - custom: - - name: OTHER_SWIFT_FLAGS - value: $(inherited) - env: false - destination: project - - name: SAMPLE_FASTLANE_PROPERTY - value: This will be available to fastlane on Beta variant - env: false - destination: fastlane + BETA: + id_suffix: beta + # If app_icon isn't specified, the value fallbacks to target.app_icon + app_icon: AppIcon.beta + version_name: 0.0.1 + version_number: 1 + + # + # 'store_destination' can be: AppStore, TestFlight or AppCenter + store_destination: AppCenter + + # + # Same as `ios.signing`, but this will override those values. + signing: + # 'match_url' isn't mandatory, only if you use Match to sign your app + # match_url: "git@github.com:sample/match.git" + team_name: "iPhone Distribution: Enterprise Sample" + team_id: "7A1234567D" + export_method: "enterprise" + + custom: + - name: OTHER_SWIFT_FLAGS + value: $(inherited) + env: false + destination: project + - name: SAMPLE_FASTLANE_PROPERTY + value: This will be available to fastlane on Beta variant + env: false + destination: fastlane signing: # 'match_url' isn't mandatory, only if you use Match to sign your app @@ -91,7 +105,12 @@ ios: team_name: "iPhone Distribution" team_id: "AB1234567D" export_method: "appstore" - + + # Should Variant try to auto detect signing identity + # if set to true, Variant will use the team_id, team_name and export_method + # to detect code signing identity from the Keychain Access + # default value is `true` + auto_detect_signing_identity: true # ---------------------------------------------------------------------- # custom: - Not required. # @@ -100,7 +119,6 @@ ios: # # Comment or delete section below if necessary. # ---------------------------------------------------------------------- - #custom: # - name: SAMPLE_PROPERTY # value: Sample value diff --git a/Tests/VariantsCoreTests/FastlaneParametersFactoryTests.swift b/Tests/VariantsCoreTests/FastlaneParametersFactoryTests.swift index e7a39087..cccbbf6b 100644 --- a/Tests/VariantsCoreTests/FastlaneParametersFactoryTests.swift +++ b/Tests/VariantsCoreTests/FastlaneParametersFactoryTests.swift @@ -131,12 +131,19 @@ class FastlaneParametersFactoryTests: XCTestCase { versionNumber: 99, appIcon: nil, appName: nil, - storeDestination: "testFlight", - custom: nil, + storeDestination: "testflight", idSuffix: "sample", bundleID: nil, - variantSigning: nil, - globalSigning: iOSSigning(teamName: "", teamID: "", exportMethod: .appstore, matchURL: ""), + globalCustomProperties: nil, + variantCustomProperties: nil, + globalSigning: iOSSigning(teamName: "", + teamID: "", + exportMethod: .appstore, + matchURL: "", + style: .manual, + autoDetectSigningIdentity: true), + debugSigning: nil, + releaseSigning: nil, globalPostSwitchScript: "echo global", variantPostSwitchScript: "echo variant") else { diff --git a/Tests/VariantsCoreTests/Mocks/MockFastlaneFactory.swift b/Tests/VariantsCoreTests/Mocks/MockFastlaneFactory.swift index 1fb8fb30..b7d8c2c8 100644 --- a/Tests/VariantsCoreTests/Mocks/MockFastlaneFactory.swift +++ b/Tests/VariantsCoreTests/Mocks/MockFastlaneFactory.swift @@ -11,18 +11,18 @@ import PathKit class MockFastlaneFactory: ParametersFactory { var createParametersCache: [(file: Path, renderTemplate: String, parameters: [CustomProperty])] = [] - var createMatchFileCache: [(variant: iOSVariant, target: iOSTarget)] = [] + var createMatchFileCache: [(variant: iOSVariant, configuration: iOSConfiguration)] = [] var renderCache: [[String: Any]] = [] var writeCache: [(data: Data, parametersFile: Path)] = [] func createParametersFile(in file: Path, renderTemplate: String, with parameters: [CustomProperty]) throws { createParametersCache.append((file: file, renderTemplate: renderTemplate, parameters: parameters)) } - - func createMatchFile(using variant: iOSVariant, target: iOSTarget) throws { - createMatchFileCache.append((variant: variant, target: target)) + + func createMatchFile(for variant: iOSVariant, configuration: iOSConfiguration) throws { + createMatchFileCache.append((variant: variant, configuration: configuration)) } - + func render(context: [String: Any], renderTemplate: String) throws -> Data? { renderCache.append(context) return nil diff --git a/Tests/VariantsCoreTests/Mocks/MockXCcodeConfigFactory.swift b/Tests/VariantsCoreTests/Mocks/MockXCcodeConfigFactory.swift deleted file mode 100644 index 6a12306c..00000000 --- a/Tests/VariantsCoreTests/Mocks/MockXCcodeConfigFactory.swift +++ /dev/null @@ -1,52 +0,0 @@ -// -// Variants -// -// Copyright (c) Backbase B.V. - https://www.backbase.com -// Created by Arthur Alves -// - -import Foundation -import PathKit -@testable import VariantsCore -// swiftlint:disable colon - -class MockXCcodeConfigFactory: XCFactory { - var writeContentCache: [(content: String, file: Path, force: Bool)] = [] - var writeJSONCache: [(encodableObject: Encodable, file: Path)] = [] - var createConfigCache: [(target: NamedTarget, - variant: iOSVariant, - xcodeProj: String?, - configPath: Path, - addToXcodeProj: Bool?)] = [] - - init(logLevel: Bool = false) { - logger = Logger(verbose: logLevel) - } - - func write(_ stringContent: String, toFile file: Path, force: Bool) -> (Bool, Path?) { - writeContentCache.append((content: stringContent, file: file, force: force)) - return (true, file) - } - - func writeJSON(_ encodableObject: T, toFile file: Path) -> (Bool, Path?) where T : Encodable { - writeJSONCache.append((encodableObject: encodableObject, file: file)) - return (true, file) - } - - func createConfig(with target: NamedTarget, - variant: iOSVariant, - xcodeProj: String?, - configPath: Path, - addToXcodeProj: Bool?) throws { - createConfigCache.append((target: target, - variant: variant, - xcodeProj: xcodeProj, - configPath: configPath, - addToXcodeProj: addToXcodeProj)) - } - - var xcconfigFileName: String = "variants.xcconfig" - var logger: Logger -} - -// swiftlint:enable colon diff --git a/Tests/VariantsCoreTests/Mocks/MockXCodeConfigFactory.swift b/Tests/VariantsCoreTests/Mocks/MockXCodeConfigFactory.swift new file mode 100644 index 00000000..b63f47c4 --- /dev/null +++ b/Tests/VariantsCoreTests/Mocks/MockXCodeConfigFactory.swift @@ -0,0 +1,41 @@ +// +// Variants +// +// Copyright (c) Backbase B.V. - https://www.backbase.com +// Created by Arthur Alves +// + +import Foundation +import PathKit +@testable import VariantsCore + +class MockXCodeConfigFactory: XCFactory { + var writeContentCache: [(content: String, file: Path, force: Bool)] = [] + var writeJSONCache: [(encodableObject: Encodable, file: Path)] = [] + var createConfigCache: [(variant: iOSVariant, configuration: iOSConfiguration, configPath: Path)] = [] + + init(logLevel: Bool = false) { + logger = Logger(verbose: logLevel) + } + + func write(_ stringContent: String, toFile file: Path, force: Bool) -> (Bool, Path?) { + writeContentCache.append((content: stringContent, file: file, force: force)) + return (true, file) + } + + func writeJSON(_ encodableObject: T, toFile file: Path) -> (Bool, Path?) where T: Encodable { + writeJSONCache.append((encodableObject: encodableObject, file: file)) + return (true, file) + } + + func createConfig(for variant: iOSVariant, configuration: iOSConfiguration, configPath: Path) throws { + createConfigCache.append(( + variant: variant, + configuration: configuration, + configPath: configPath + )) + } + + var xcconfigFileName: String = "variants.xcconfig" + var logger: Logger +} diff --git a/Tests/VariantsCoreTests/Resources/invalid_variants.yml b/Tests/VariantsCoreTests/Resources/invalid_variants.yml index 7777d826..ed07dd97 100644 --- a/Tests/VariantsCoreTests/Resources/invalid_variants.yml +++ b/Tests/VariantsCoreTests/Resources/invalid_variants.yml @@ -51,16 +51,15 @@ android: ios: xcodeproj: FrankBank.xcodeproj - targets: - FrankBank: - name: FrankBank - bundle_id: com.backbase.frank.ios - test_target: FrankBankTests - app_icon: AppIcon - source: - path: Sources - info: Sources/Info.plist - config: Sources + target: + name: FrankBank + bundle_id: com.backbase.frank.ios + test_target: FrankBankTests + app_icon: AppIcon + source: + path: Sources + info: Sources/Info.plist + config: Sources variants: default: version_name: 0.0.1 diff --git a/Tests/VariantsCoreTests/Resources/ios/VariantsTestApp.xcodeproj/project.pbxproj b/Tests/VariantsCoreTests/Resources/ios/VariantsTestApp.xcodeproj/project.pbxproj new file mode 100644 index 00000000..980f0dad --- /dev/null +++ b/Tests/VariantsCoreTests/Resources/ios/VariantsTestApp.xcodeproj/project.pbxproj @@ -0,0 +1,705 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 56; + objects = { + +/* Begin PBXBuildFile section */ + 2DFD1E682D3FE3B200349BF3 /* WidgetKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 2DFD1E672D3FE3B200349BF3 /* WidgetKit.framework */; }; + 2DFD1E6A2D3FE3B200349BF3 /* SwiftUI.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 2DFD1E692D3FE3B200349BF3 /* SwiftUI.framework */; }; + 2DFD1E6D2D3FE3B200349BF3 /* VariantsWidgetExtensionBundle.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2DFD1E6C2D3FE3B200349BF3 /* VariantsWidgetExtensionBundle.swift */; }; + 2DFD1E6F2D3FE3B200349BF3 /* VariantsWidgetExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2DFD1E6E2D3FE3B200349BF3 /* VariantsWidgetExtension.swift */; }; + 2DFD1E712D3FE3B400349BF3 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 2DFD1E702D3FE3B400349BF3 /* Assets.xcassets */; }; + 2DFD1E752D3FE3B400349BF3 /* VariantsWidgetExtensionExtension.appex in Embed Foundation Extensions */ = {isa = PBXBuildFile; fileRef = 2DFD1E652D3FE3B200349BF3 /* VariantsWidgetExtensionExtension.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; }; + 2DFD1E812D3FE6ED00349BF3 /* VariantsTestAppTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2DFD1E802D3FE6ED00349BF3 /* VariantsTestAppTests.swift */; }; + 42717DE08F8D9A239BB8BDD3 /* Variants.swift in Sources */ = {isa = PBXBuildFile; fileRef = B512AE731F1EAB7155E4C339 /* Variants.swift */; }; + 8E00D0E729967BD4009F995B /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8E00D0E629967BD4009F995B /* AppDelegate.swift */; }; + 8E00D0E929967BD4009F995B /* SceneDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8E00D0E829967BD4009F995B /* SceneDelegate.swift */; }; + 8E00D0EB29967BD4009F995B /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8E00D0EA29967BD4009F995B /* ViewController.swift */; }; + 8E00D0EE29967BD4009F995B /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 8E00D0EC29967BD4009F995B /* Main.storyboard */; }; + 8E00D0F029967BD5009F995B /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 8E00D0EF29967BD5009F995B /* Assets.xcassets */; }; + 8E00D0F329967BD5009F995B /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 8E00D0F129967BD5009F995B /* LaunchScreen.storyboard */; }; +/* End PBXBuildFile section */ + +/* Begin PBXContainerItemProxy section */ + 2DFD1E732D3FE3B400349BF3 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 8E00D0DB29967BD4009F995B /* Project object */; + proxyType = 1; + remoteGlobalIDString = 2DFD1E642D3FE3B200349BF3; + remoteInfo = VariantsWidgetExtensionExtension; + }; + 2DFD1E822D3FE6ED00349BF3 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 8E00D0DB29967BD4009F995B /* Project object */; + proxyType = 1; + remoteGlobalIDString = 8E00D0E229967BD4009F995B; + remoteInfo = VariantsTestApp; + }; +/* End PBXContainerItemProxy section */ + +/* Begin PBXCopyFilesBuildPhase section */ + 2DFD1E792D3FE3B400349BF3 /* Embed Foundation Extensions */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 2147483647; + dstPath = ""; + dstSubfolderSpec = 13; + files = ( + 2DFD1E752D3FE3B400349BF3 /* VariantsWidgetExtensionExtension.appex in Embed Foundation Extensions */, + ); + name = "Embed Foundation Extensions"; + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXCopyFilesBuildPhase section */ + +/* Begin PBXFileReference section */ + 2DFD1E652D3FE3B200349BF3 /* VariantsWidgetExtensionExtension.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = VariantsWidgetExtensionExtension.appex; sourceTree = BUILT_PRODUCTS_DIR; }; + 2DFD1E672D3FE3B200349BF3 /* WidgetKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = WidgetKit.framework; path = System/Library/Frameworks/WidgetKit.framework; sourceTree = SDKROOT; }; + 2DFD1E692D3FE3B200349BF3 /* SwiftUI.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = SwiftUI.framework; path = System/Library/Frameworks/SwiftUI.framework; sourceTree = SDKROOT; }; + 2DFD1E6C2D3FE3B200349BF3 /* VariantsWidgetExtensionBundle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VariantsWidgetExtensionBundle.swift; sourceTree = ""; }; + 2DFD1E6E2D3FE3B200349BF3 /* VariantsWidgetExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VariantsWidgetExtension.swift; sourceTree = ""; }; + 2DFD1E702D3FE3B400349BF3 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; + 2DFD1E722D3FE3B400349BF3 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + 2DFD1E7E2D3FE6ED00349BF3 /* VariantsTestAppTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = VariantsTestAppTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; + 2DFD1E802D3FE6ED00349BF3 /* VariantsTestAppTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VariantsTestAppTests.swift; sourceTree = ""; }; + 2DFD1E992D3FEB9000349BF3 /* VariantsTestApp copy-Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; name = "VariantsTestApp copy-Info.plist"; path = "/Users/gabriel.minucci/Documents/Backbase/variants/samples/ios/VariantsTestApp/VariantsTestApp copy-Info.plist"; sourceTree = ""; }; + 78DBE119A2B49BE740D07F6E /* variants.xcconfig */ = {isa = PBXFileReference; explicitFileType = text.xcconfig; lastKnownFileType = text.xcconfig; name = variants.xcconfig; path = variants.xcconfig; sourceTree = ""; }; + 8E00D0E329967BD4009F995B /* .app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = .app; sourceTree = BUILT_PRODUCTS_DIR; }; + 8E00D0E629967BD4009F995B /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; + 8E00D0E829967BD4009F995B /* SceneDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SceneDelegate.swift; sourceTree = ""; }; + 8E00D0EA29967BD4009F995B /* ViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewController.swift; sourceTree = ""; }; + 8E00D0ED29967BD4009F995B /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; + 8E00D0EF29967BD5009F995B /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; + 8E00D0F229967BD5009F995B /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; + 8E00D0F429967BD5009F995B /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + B512AE731F1EAB7155E4C339 /* Variants.swift */ = {isa = PBXFileReference; explicitFileType = sourcecode.swift; lastKnownFileType = sourcecode.swift; name = Variants.swift; path = Variants.swift; sourceTree = ""; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + 2DFD1E622D3FE3B200349BF3 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 2DFD1E6A2D3FE3B200349BF3 /* SwiftUI.framework in Frameworks */, + 2DFD1E682D3FE3B200349BF3 /* WidgetKit.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 2DFD1E7B2D3FE6ED00349BF3 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 8E00D0E029967BD4009F995B /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 2DFD1E662D3FE3B200349BF3 /* Frameworks */ = { + isa = PBXGroup; + children = ( + 2DFD1E672D3FE3B200349BF3 /* WidgetKit.framework */, + 2DFD1E692D3FE3B200349BF3 /* SwiftUI.framework */, + ); + name = Frameworks; + sourceTree = ""; + }; + 2DFD1E6B2D3FE3B200349BF3 /* VariantsWidgetExtension */ = { + isa = PBXGroup; + children = ( + 2DFD1E6C2D3FE3B200349BF3 /* VariantsWidgetExtensionBundle.swift */, + 2DFD1E6E2D3FE3B200349BF3 /* VariantsWidgetExtension.swift */, + 2DFD1E702D3FE3B400349BF3 /* Assets.xcassets */, + 2DFD1E722D3FE3B400349BF3 /* Info.plist */, + ); + path = VariantsWidgetExtension; + sourceTree = ""; + }; + 2DFD1E7F2D3FE6ED00349BF3 /* VariantsTestAppTests */ = { + isa = PBXGroup; + children = ( + 2DFD1E802D3FE6ED00349BF3 /* VariantsTestAppTests.swift */, + ); + path = VariantsTestAppTests; + sourceTree = ""; + }; + 3C00DF7AC3E9535AC87DE841 /* Variants */ = { + isa = PBXGroup; + children = ( + 78DBE119A2B49BE740D07F6E /* variants.xcconfig */, + B512AE731F1EAB7155E4C339 /* Variants.swift */, + ); + name = Variants; + path = Variants; + sourceTree = ""; + }; + 8E00D0DA29967BD4009F995B = { + isa = PBXGroup; + children = ( + 8E00D0E529967BD4009F995B /* VariantsTestApp */, + 2DFD1E6B2D3FE3B200349BF3 /* VariantsWidgetExtension */, + 2DFD1E7F2D3FE6ED00349BF3 /* VariantsTestAppTests */, + 2DFD1E662D3FE3B200349BF3 /* Frameworks */, + 8E00D0E429967BD4009F995B /* Products */, + 2DFD1E992D3FEB9000349BF3 /* VariantsTestApp copy-Info.plist */, + ); + sourceTree = ""; + }; + 8E00D0E429967BD4009F995B /* Products */ = { + isa = PBXGroup; + children = ( + 8E00D0E329967BD4009F995B /* .app */, + 2DFD1E652D3FE3B200349BF3 /* VariantsWidgetExtensionExtension.appex */, + 2DFD1E7E2D3FE6ED00349BF3 /* VariantsTestAppTests.xctest */, + ); + name = Products; + sourceTree = ""; + }; + 8E00D0E529967BD4009F995B /* VariantsTestApp */ = { + isa = PBXGroup; + children = ( + 8E00D0E629967BD4009F995B /* AppDelegate.swift */, + 8E00D0E829967BD4009F995B /* SceneDelegate.swift */, + 8E00D0EA29967BD4009F995B /* ViewController.swift */, + 8E00D0EC29967BD4009F995B /* Main.storyboard */, + 8E00D0EF29967BD5009F995B /* Assets.xcassets */, + 8E00D0F129967BD5009F995B /* LaunchScreen.storyboard */, + 8E00D0F429967BD5009F995B /* Info.plist */, + 3C00DF7AC3E9535AC87DE841 /* Variants */, + ); + path = VariantsTestApp; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + 2DFD1E642D3FE3B200349BF3 /* VariantsWidgetExtensionExtension */ = { + isa = PBXNativeTarget; + buildConfigurationList = 2DFD1E762D3FE3B400349BF3 /* Build configuration list for PBXNativeTarget "VariantsWidgetExtensionExtension" */; + buildPhases = ( + 2DFD1E612D3FE3B200349BF3 /* Sources */, + 2DFD1E622D3FE3B200349BF3 /* Frameworks */, + 2DFD1E632D3FE3B200349BF3 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = VariantsWidgetExtensionExtension; + productName = VariantsWidgetExtensionExtension; + productReference = 2DFD1E652D3FE3B200349BF3 /* VariantsWidgetExtensionExtension.appex */; + productType = "com.apple.product-type.app-extension"; + }; + 2DFD1E7D2D3FE6ED00349BF3 /* VariantsTestAppTests */ = { + isa = PBXNativeTarget; + buildConfigurationList = 2DFD1E842D3FE6ED00349BF3 /* Build configuration list for PBXNativeTarget "VariantsTestAppTests" */; + buildPhases = ( + 2DFD1E7A2D3FE6ED00349BF3 /* Sources */, + 2DFD1E7B2D3FE6ED00349BF3 /* Frameworks */, + 2DFD1E7C2D3FE6ED00349BF3 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + 2DFD1E832D3FE6ED00349BF3 /* PBXTargetDependency */, + ); + name = VariantsTestAppTests; + productName = VariantsTestAppTests; + productReference = 2DFD1E7E2D3FE6ED00349BF3 /* VariantsTestAppTests.xctest */; + productType = "com.apple.product-type.bundle.unit-test"; + }; + 8E00D0E229967BD4009F995B /* VariantsTestApp */ = { + isa = PBXNativeTarget; + buildConfigurationList = 8E00D0F729967BD5009F995B /* Build configuration list for PBXNativeTarget "VariantsTestApp" */; + buildPhases = ( + 8E00D0DF29967BD4009F995B /* Sources */, + 8E00D0E029967BD4009F995B /* Frameworks */, + 8E00D0E129967BD4009F995B /* Resources */, + 2DFD1E792D3FE3B400349BF3 /* Embed Foundation Extensions */, + ); + buildRules = ( + ); + dependencies = ( + 2DFD1E742D3FE3B400349BF3 /* PBXTargetDependency */, + ); + name = VariantsTestApp; + productName = VariantsTestApp; + productReference = 8E00D0E329967BD4009F995B /* .app */; + productType = "com.apple.product-type.application"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + 8E00D0DB29967BD4009F995B /* Project object */ = { + isa = PBXProject; + attributes = { + BuildIndependentTargetsInParallel = 1; + LastSwiftUpdateCheck = 1540; + LastUpgradeCheck = 1410; + TargetAttributes = { + 2DFD1E642D3FE3B200349BF3 = { + CreatedOnToolsVersion = 15.4; + }; + 2DFD1E7D2D3FE6ED00349BF3 = { + CreatedOnToolsVersion = 15.4; + TestTargetID = 8E00D0E229967BD4009F995B; + }; + 8E00D0E229967BD4009F995B = { + CreatedOnToolsVersion = 14.1; + }; + }; + }; + buildConfigurationList = 8E00D0DE29967BD4009F995B /* Build configuration list for PBXProject "VariantsTestApp" */; + compatibilityVersion = "Xcode 14.0"; + developmentRegion = en; + hasScannedForEncodings = 0; + knownRegions = ( + en, + Base, + ); + mainGroup = 8E00D0DA29967BD4009F995B; + productRefGroup = 8E00D0E429967BD4009F995B /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + 8E00D0E229967BD4009F995B /* VariantsTestApp */, + 2DFD1E642D3FE3B200349BF3 /* VariantsWidgetExtensionExtension */, + 2DFD1E7D2D3FE6ED00349BF3 /* VariantsTestAppTests */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + 2DFD1E632D3FE3B200349BF3 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 2DFD1E712D3FE3B400349BF3 /* Assets.xcassets in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 2DFD1E7C2D3FE6ED00349BF3 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 8E00D0E129967BD4009F995B /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 8E00D0F329967BD5009F995B /* LaunchScreen.storyboard in Resources */, + 8E00D0F029967BD5009F995B /* Assets.xcassets in Resources */, + 8E00D0EE29967BD4009F995B /* Main.storyboard in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + 2DFD1E612D3FE3B200349BF3 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 2DFD1E6D2D3FE3B200349BF3 /* VariantsWidgetExtensionBundle.swift in Sources */, + 2DFD1E6F2D3FE3B200349BF3 /* VariantsWidgetExtension.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 2DFD1E7A2D3FE6ED00349BF3 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 2DFD1E812D3FE6ED00349BF3 /* VariantsTestAppTests.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 8E00D0DF29967BD4009F995B /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 8E00D0EB29967BD4009F995B /* ViewController.swift in Sources */, + 8E00D0E729967BD4009F995B /* AppDelegate.swift in Sources */, + 8E00D0E929967BD4009F995B /* SceneDelegate.swift in Sources */, + 42717DE08F8D9A239BB8BDD3 /* Variants.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin PBXTargetDependency section */ + 2DFD1E742D3FE3B400349BF3 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 2DFD1E642D3FE3B200349BF3 /* VariantsWidgetExtensionExtension */; + targetProxy = 2DFD1E732D3FE3B400349BF3 /* PBXContainerItemProxy */; + }; + 2DFD1E832D3FE6ED00349BF3 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 8E00D0E229967BD4009F995B /* VariantsTestApp */; + targetProxy = 2DFD1E822D3FE6ED00349BF3 /* PBXContainerItemProxy */; + }; +/* End PBXTargetDependency section */ + +/* Begin PBXVariantGroup section */ + 8E00D0EC29967BD4009F995B /* Main.storyboard */ = { + isa = PBXVariantGroup; + children = ( + 8E00D0ED29967BD4009F995B /* Base */, + ); + name = Main.storyboard; + sourceTree = ""; + }; + 8E00D0F129967BD5009F995B /* LaunchScreen.storyboard */ = { + isa = PBXVariantGroup; + children = ( + 8E00D0F229967BD5009F995B /* Base */, + ); + name = LaunchScreen.storyboard; + sourceTree = ""; + }; +/* End PBXVariantGroup section */ + +/* Begin XCBuildConfiguration section */ + 2DFD1E772D3FE3B400349BF3 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; + ASSETCATALOG_COMPILER_WIDGET_BACKGROUND_COLOR_NAME = WidgetBackground; + CODE_SIGN_IDENTITY = "Apple Distribution: Backbase B.V. (ABC1234567D)"; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + DEVELOPMENT_TEAM = R22WT7DX79; + ENABLE_USER_SCRIPT_SANDBOXING = YES; + GCC_C_LANGUAGE_STANDARD = gnu17; + GENERATE_INFOPLIST_FILE = YES; + INFOPLIST_FILE = VariantsWidgetExtension/Info.plist; + INFOPLIST_KEY_CFBundleDisplayName = VariantsWidgetExtension; + INFOPLIST_KEY_NSHumanReadableCopyright = ""; + IPHONEOS_DEPLOYMENT_TARGET = 17.5; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@executable_path/../../Frameworks", + ); + LOCALIZATION_PREFERS_STRING_CATALOGS = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.backbase.VariantsTestApp.beta.VariantsWidgetExtension; + PRODUCT_NAME = "$(TARGET_NAME)"; + SKIP_INSTALL = YES; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = "DEBUG $(inherited)"; + SWIFT_EMIT_LOC_STRINGS = YES; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Debug; + }; + 2DFD1E782D3FE3B400349BF3 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; + ASSETCATALOG_COMPILER_WIDGET_BACKGROUND_COLOR_NAME = WidgetBackground; + CODE_SIGN_IDENTITY = "Apple Distribution: Backbase B.V. (ABC1234567D)"; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + DEVELOPMENT_TEAM = R22WT7DX79; + ENABLE_USER_SCRIPT_SANDBOXING = YES; + GCC_C_LANGUAGE_STANDARD = gnu17; + GENERATE_INFOPLIST_FILE = YES; + INFOPLIST_FILE = VariantsWidgetExtension/Info.plist; + INFOPLIST_KEY_CFBundleDisplayName = VariantsWidgetExtension; + INFOPLIST_KEY_NSHumanReadableCopyright = ""; + IPHONEOS_DEPLOYMENT_TARGET = 17.5; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@executable_path/../../Frameworks", + ); + LOCALIZATION_PREFERS_STRING_CATALOGS = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.backbase.VariantsTestApp.beta.VariantsWidgetExtension; + PRODUCT_NAME = "$(TARGET_NAME)"; + SKIP_INSTALL = YES; + SWIFT_EMIT_LOC_STRINGS = YES; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Release; + }; + 2DFD1E852D3FE6ED00349BF3 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + BUNDLE_LOADER = "$(TEST_HOST)"; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + ENABLE_USER_SCRIPT_SANDBOXING = YES; + GCC_C_LANGUAGE_STANDARD = gnu17; + GENERATE_INFOPLIST_FILE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 17.5; + LOCALIZATION_PREFERS_STRING_CATALOGS = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.backbase.VariantsTestApp.beta.VariantsTestAppTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = "DEBUG $(inherited)"; + SWIFT_EMIT_LOC_STRINGS = NO; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/VariantsTestApp BETA.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/VariantsTestApp BETA"; + }; + name = Debug; + }; + 2DFD1E862D3FE6ED00349BF3 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + BUNDLE_LOADER = "$(TEST_HOST)"; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + ENABLE_USER_SCRIPT_SANDBOXING = YES; + GCC_C_LANGUAGE_STANDARD = gnu17; + GENERATE_INFOPLIST_FILE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 17.5; + LOCALIZATION_PREFERS_STRING_CATALOGS = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.backbase.VariantsTestApp.beta.VariantsTestAppTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_EMIT_LOC_STRINGS = NO; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/VariantsTestApp BETA.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/VariantsTestApp BETA"; + }; + name = Release; + }; + 8E00D0F529967BD5009F995B /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = dwarf; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 16.1; + MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; + MTL_FAST_MATH = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = iphoneos; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + }; + name = Debug; + }; + 8E00D0F629967BD5009F995B /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 16.1; + MTL_ENABLE_DEBUG_INFO = NO; + MTL_FAST_MATH = YES; + SDKROOT = iphoneos; + SWIFT_COMPILATION_MODE = wholemodule; + SWIFT_OPTIMIZATION_LEVEL = "-O"; + VALIDATE_PRODUCT = YES; + }; + name = Release; + }; + 8E00D0F829967BD5009F995B /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 78DBE119A2B49BE740D07F6E /* variants.xcconfig */; + buildSettings = { + ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; + ASSETCATALOG_COMPILER_APPICON_NAME = "$(V_APP_ICON)"; + ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; + CODE_SIGN_IDENTITY = "Apple Distribution: Backbase B.V. (ABC1234567D)"; + CODE_SIGN_STYLE = Manual; + CURRENT_PROJECT_VERSION = 1; + DEVELOPMENT_TEAM = R22WT7DX79; + GENERATE_INFOPLIST_FILE = YES; + INFOPLIST_FILE = VariantsTestApp/Info.plist; + INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; + INFOPLIST_KEY_UILaunchStoryboardName = LaunchScreen; + INFOPLIST_KEY_UIMainStoryboardFile = Main; + INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; + INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = "$(V_BUNDLE_ID)"; + PRODUCT_NAME = "$(V_APP_NAME)"; + PROVISIONING_PROFILE_SPECIFIER = "$(V_MATCH_PROFILE)"; + SWIFT_EMIT_LOC_STRINGS = YES; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Debug; + }; + 8E00D0F929967BD5009F995B /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 78DBE119A2B49BE740D07F6E /* variants.xcconfig */; + buildSettings = { + ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; + ASSETCATALOG_COMPILER_APPICON_NAME = "$(V_APP_ICON)"; + ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; + CODE_SIGN_IDENTITY = "Apple Distribution: Backbase B.V. (ABC1234567D)"; + CODE_SIGN_STYLE = Manual; + CURRENT_PROJECT_VERSION = 1; + DEVELOPMENT_TEAM = R22WT7DX79; + GENERATE_INFOPLIST_FILE = YES; + INFOPLIST_FILE = VariantsTestApp/Info.plist; + INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; + INFOPLIST_KEY_UILaunchStoryboardName = LaunchScreen; + INFOPLIST_KEY_UIMainStoryboardFile = Main; + INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; + INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = "$(V_BUNDLE_ID)"; + PRODUCT_NAME = "$(V_APP_NAME)"; + PROVISIONING_PROFILE_SPECIFIER = "$(V_MATCH_PROFILE)"; + SWIFT_EMIT_LOC_STRINGS = YES; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 2DFD1E762D3FE3B400349BF3 /* Build configuration list for PBXNativeTarget "VariantsWidgetExtensionExtension" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 2DFD1E772D3FE3B400349BF3 /* Debug */, + 2DFD1E782D3FE3B400349BF3 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 2DFD1E842D3FE6ED00349BF3 /* Build configuration list for PBXNativeTarget "VariantsTestAppTests" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 2DFD1E852D3FE6ED00349BF3 /* Debug */, + 2DFD1E862D3FE6ED00349BF3 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 8E00D0DE29967BD4009F995B /* Build configuration list for PBXProject "VariantsTestApp" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 8E00D0F529967BD5009F995B /* Debug */, + 8E00D0F629967BD5009F995B /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 8E00D0F729967BD5009F995B /* Build configuration list for PBXNativeTarget "VariantsTestApp" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 8E00D0F829967BD5009F995B /* Debug */, + 8E00D0F929967BD5009F995B /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = 8E00D0DB29967BD4009F995B /* Project object */; +} diff --git a/Tests/VariantsCoreTests/Resources/ios/VariantsTestApp.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/Tests/VariantsCoreTests/Resources/ios/VariantsTestApp.xcodeproj/project.xcworkspace/contents.xcworkspacedata new file mode 100644 index 00000000..919434a6 --- /dev/null +++ b/Tests/VariantsCoreTests/Resources/ios/VariantsTestApp.xcodeproj/project.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/Tests/VariantsCoreTests/Resources/ios/VariantsTestApp.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/Tests/VariantsCoreTests/Resources/ios/VariantsTestApp.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 00000000..18d98100 --- /dev/null +++ b/Tests/VariantsCoreTests/Resources/ios/VariantsTestApp.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/Tests/VariantsCoreTests/Resources/ios/VariantsTestApp.xcodeproj/xcshareddata/xcschemes/VariantsTestApp.xcscheme b/Tests/VariantsCoreTests/Resources/ios/VariantsTestApp.xcodeproj/xcshareddata/xcschemes/VariantsTestApp.xcscheme new file mode 100644 index 00000000..d8849f33 --- /dev/null +++ b/Tests/VariantsCoreTests/Resources/ios/VariantsTestApp.xcodeproj/xcshareddata/xcschemes/VariantsTestApp.xcscheme @@ -0,0 +1,78 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Tests/VariantsCoreTests/Resources/ios/VariantsTestApp.xcodeproj/xcshareddata/xcschemes/VariantsWidgetExtensionExtension.xcscheme b/Tests/VariantsCoreTests/Resources/ios/VariantsTestApp.xcodeproj/xcshareddata/xcschemes/VariantsWidgetExtensionExtension.xcscheme new file mode 100644 index 00000000..689002de --- /dev/null +++ b/Tests/VariantsCoreTests/Resources/ios/VariantsTestApp.xcodeproj/xcshareddata/xcschemes/VariantsWidgetExtensionExtension.xcscheme @@ -0,0 +1,112 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Tests/VariantsCoreTests/Resources/ios/invalid_incomplete_signing_configuration.yml b/Tests/VariantsCoreTests/Resources/ios/invalid_incomplete_signing_configuration.yml index 38d2a9f5..e7868a0e 100644 --- a/Tests/VariantsCoreTests/Resources/ios/invalid_incomplete_signing_configuration.yml +++ b/Tests/VariantsCoreTests/Resources/ios/invalid_incomplete_signing_configuration.yml @@ -4,53 +4,52 @@ ios: xcodeproj: FrankBank.xcodeproj - targets: - FrankBank: + target: name: FrankBank bundle_id: com.backbase.frank.ios test_target: FrankBankTests app_icon: AppIcon source: - path: Sources - info: Sources/Info.plist - config: Sources + path: Sources + info: Sources/Info.plist + config: Sources variants: - default: - version_name: 0.0.1 - version_number: 1 - signing: - team_name: "BACKBASE EUROPE B.V." - team_id: "AB123456CD" - match_url: "git@github.com:sample/match.git" - export_method: appstore - custom: - - name: SAMPLE_CONFIG - value: Production Value - destination: project - BETA: - id_suffix: beta - app_icon: AppIcon.beta - version_name: 0.0.1 - version_number: 1 - store_destination: AppCenter - custom: - - name: SAMPLE_CONFIG - value: BETA Value - destination: fastlane - STG: - id_suffix: staging - version_name: 0.0.1 - version_number: 1 - store_destination: AppCenter - signing: - team_name: "iPhone Distribution: BACKBASE EUROPE B.V." - team_id: "AB123456CD" - match_url: "git@github.com:sample/enterprise-match.git" - export_method: enterprise - custom: - - name: SAMPLE_CONFIG - value: STG Value - destination: project + default: + version_name: 0.0.1 + version_number: 1 + signing: + team_name: "BACKBASE EUROPE B.V." + team_id: "AB123456CD" + match_url: "git@github.com:sample/match.git" + export_method: appstore + custom: + - name: SAMPLE_CONFIG + value: Production Value + destination: project + BETA: + id_suffix: beta + app_icon: AppIcon.beta + version_name: 0.0.1 + version_number: 1 + store_destination: AppCenter + custom: + - name: SAMPLE_CONFIG + value: BETA Value + destination: fastlane + STG: + id_suffix: staging + version_name: 0.0.1 + version_number: 1 + store_destination: AppCenter + signing: + team_name: "iPhone Distribution: BACKBASE EUROPE B.V." + team_id: "AB123456CD" + match_url: "git@github.com:sample/enterprise-match.git" + export_method: enterprise + custom: + - name: SAMPLE_CONFIG + value: STG Value + destination: project custom: - name: SAMPLE_GLOBAL diff --git a/Tests/VariantsCoreTests/Resources/ios/invalid_missing_export_method.yml b/Tests/VariantsCoreTests/Resources/ios/invalid_missing_export_method.yml index c6457b2d..8563f4e4 100644 --- a/Tests/VariantsCoreTests/Resources/ios/invalid_missing_export_method.yml +++ b/Tests/VariantsCoreTests/Resources/ios/invalid_missing_export_method.yml @@ -4,51 +4,50 @@ ios: xcodeproj: FrankBank.xcodeproj - targets: - FrankBank: + target: name: FrankBank bundle_id: com.backbase.frank.ios test_target: FrankBankTests app_icon: AppIcon source: - path: Sources - info: Sources/Info.plist - config: Sources + path: Sources + info: Sources/Info.plist + config: Sources variants: - default: - version_name: 0.0.1 - version_number: 1 - signing: - team_name: "BACKBASE EUROPE B.V." - team_id: "AB123456CD" - match_url: "git@github.com:sample/match.git" - export_method: appstore - custom: - - name: SAMPLE_CONFIG - value: Production Value - destination: project - BETA: - id_suffix: beta - app_icon: AppIcon.beta - version_name: 0.0.1 - version_number: 1 - store_destination: AppCenter - custom: - - name: SAMPLE_CONFIG - value: BETA Value - destination: fastlane - STG: - id_suffix: staging - version_name: 0.0.1 - version_number: 1 - store_destination: AppCenter - signing: - match_url: "git@github.com:sample/enterprise-match.git" - export_method: enterprise - custom: - - name: SAMPLE_CONFIG - value: STG Value - destination: project + default: + version_name: 0.0.1 + version_number: 1 + signing: + team_name: "BACKBASE EUROPE B.V." + team_id: "AB123456CD" + match_url: "git@github.com:sample/match.git" + export_method: appstore + custom: + - name: SAMPLE_CONFIG + value: Production Value + destination: project + BETA: + id_suffix: beta + app_icon: AppIcon.beta + version_name: 0.0.1 + version_number: 1 + store_destination: AppCenter + custom: + - name: SAMPLE_CONFIG + value: BETA Value + destination: fastlane + STG: + id_suffix: staging + version_name: 0.0.1 + version_number: 1 + store_destination: AppCenter + signing: + match_url: "git@github.com:sample/enterprise-match.git" + export_method: enterprise + custom: + - name: SAMPLE_CONFIG + value: STG Value + destination: project signing: team_name: "iPhone Distribution: BACKBASE EUROPE B.V." diff --git a/Tests/VariantsCoreTests/Resources/valid_variants.yml b/Tests/VariantsCoreTests/Resources/valid_variants.yml index 6d77e033..5e3edb8e 100644 --- a/Tests/VariantsCoreTests/Resources/valid_variants.yml +++ b/Tests/VariantsCoreTests/Resources/valid_variants.yml @@ -50,51 +50,60 @@ android: ios: xcodeproj: FrankBank.xcodeproj - targets: - FrankBank: + target: name: FrankBank bundle_id: com.backbase.frank.ios test_target: FrankBankTests app_icon: AppIcon source: - path: Sources - info: Sources/Info.plist - config: Sources + path: Sources + info: Sources/Info.plist + config: Sources + extensions: + - name: VariantsWidgetExtension + bundle_suffix: VariantsWidgetExtension + signed: true + - name: VariantsWidgetExtension2 + bundle_id: com.backbase.frank.io.VariantsWidgetExtension2 + signed: true + - name: OtherExtension + bundle_id: com.variantsTest.OtherExtension + signed: false variants: - default: - version_name: 0.0.1 - version_number: 1 - signing: - team_name: "BACKBASE EUROPE B.V." - team_id: "AB123456CD" - match_url: "git@github.com:sample/match.git" - export_method: appstore - custom: - - name: SAMPLE_CONFIG - value: Production Value - destination: project - BETA: - id_suffix: beta - app_icon: AppIcon.beta - version_name: 0.0.1 - version_number: 1 - store_destination: AppCenter - custom: - - name: SAMPLE_CONFIG - value: BETA Value - destination: fastlane - STG: - id_suffix: staging - version_name: 0.0.1 - version_number: 1 - store_destination: AppCenter - signing: - match_url: "git@github.com:sample/enterprise-match.git" - export_method: enterprise - custom: - - name: SAMPLE_CONFIG - value: STG Value - destination: project + default: + version_name: 0.0.1 + version_number: 1 + signing: + team_name: "BACKBASE EUROPE B.V." + team_id: "AB123456CD" + match_url: "git@github.com:sample/match.git" + export_method: appstore + custom: + - name: SAMPLE_CONFIG + value: Production Value + destination: project + BETA: + id_suffix: beta + app_icon: AppIcon.beta + version_name: 0.0.1 + version_number: 1 + store_destination: AppCenter + custom: + - name: SAMPLE_CONFIG + value: BETA Value + destination: fastlane + STG: + id_suffix: staging + version_name: 0.0.1 + version_number: 1 + store_destination: AppCenter + signing: + match_url: "git@github.com:sample/enterprise-match.git" + export_method: enterprise + custom: + - name: SAMPLE_CONFIG + value: STG Value + destination: project signing: team_name: "iPhone Distribution: BACKBASE EUROPE B.V." diff --git a/Tests/VariantsCoreTests/Resources/variants-template.yml b/Tests/VariantsCoreTests/Resources/variants-template.yml index 4a861957..43328c2d 100644 --- a/Tests/VariantsCoreTests/Resources/variants-template.yml +++ b/Tests/VariantsCoreTests/Resources/variants-template.yml @@ -5,51 +5,111 @@ ios: xcodeproj: {{ PROJECT }}.xcodeproj - targets: - {{ TARGET }}: + target: name: {{ APP_NAME }} bundle_id: {{ APP_BUNDLE_ID }} test_target: {{ TEST_TARGET }} app_icon: {{ APP_ICON }} source: - path: {{ SOURCE }} - info: {{ INFO_PLIST }} - config: {{ SOURCE }} + path: {{ SOURCE }} + info: {{ INFO_PLIST }} + config: {{ SOURCE }} + # ---------------------------------------------------------------------- + # This can be used to add a list of target extensions to be included in + # the signing phase, both in the Matchfile and Xcode target + # + # Comment or delete section below if necessary. + # ---------------------------------------------------------------------- + #extensions: + # - name: MyAppExtension + # bundle_id: com.myApp.MyAppExtension + # signed: true variants: - # Default variant is mandatory, do not remove - default: - version_name: 0.0.1 - version_number: 1 + # Default variant is mandatory, do not remove + default: + version_name: 0.0.1 + version_number: 1 + # + # 'store_destination' can be: AppStore, TestFlight or AppCenter + store_destination: AppStore + + # + # Same as `ios.signing`, but this will override those values. + signing: + # 'match_url' isn't mandatory, only if you use Match to sign your app + # match_url: "git@github.com:sample/match.git" + team_name: "iPhone Distribution" + team_id: "AB1234567D" + + # + # custom: - Not required. + # + # You can have as many custom fields as possible. + # Only strings allowed. + # + # The value of will be written to 1 of 2 possible destinations: + # - project => variants.xcconfig + # - fastlane => fastlane/parameters/variants_params.rb + # + custom: + - name: OTHER_SWIFT_FLAGS + value: $(inherited) + env: false + destination: project + - name: SAMPLE_FASTLANE_PROPERTY + value: This will be available to fastlane + env: false + destination: fastlane # - # custom: - Not required. - # You can have as many custom fields as possible. - # Only strings allowed. + # Sample variant, "beta". + # Only `version_name` and `version_number` are mandatory fields # - custom: - - name: OTHER_SWIFT_FLAGS - value: $(inherited) - env: false - destination: project - - name: SAMPLE_FASTLANE_PROPERTY - value: This will be available to fastlane - env: false - destination: fastlane - # - # Sample variant, "beta". - # Only `name` and `id_suffix` are mandatory fields - # - BETA: - id_suffix: beta - # If app_icon isn't specified, the value fallbacks to target.app_icon - app_icon: AppIcon.beta - version_name: 0.0.1 - version_number: 1 - custom: - - name: OTHER_SWIFT_FLAGS - value: $(inherited) - env: false - destination: project - - name: SAMPLE_FASTLANE_PROPERTY - value: This will be available to fastlane on Beta variant - env: false - destination: fastlane + BETA: + id_suffix: beta + # If app_icon isn't specified, the value fallbacks to target.app_icon + app_icon: AppIcon.beta + version_name: 0.0.1 + version_number: 1 + + # + # 'store_destination' can be: AppStore, TestFlight or AppCenter + store_destination: AppCenter + + # + # Same as `ios.signing`, but this will override those values. + signing: + # 'match_url' isn't mandatory, only if you use Match to sign your app + # match_url: "git@github.com:sample/match.git" + team_name: "iPhone Distribution: Enterprise Sample" + team_id: "7A1234567D" + export_method: "enterprise" + + custom: + - name: OTHER_SWIFT_FLAGS + value: $(inherited) + env: false + destination: project + - name: SAMPLE_FASTLANE_PROPERTY + value: This will be available to fastlane on Beta variant + env: false + destination: fastlane + + signing: + # 'match_url' isn't mandatory, only if you use Match to sign your app + #match_url: "git@github.com:sample/match.git" + team_name: "iPhone Distribution" + team_id: "AB1234567D" + export_method: "appstore" + + # ---------------------------------------------------------------------- + # custom: - Not required. + # + # Same as variant's `custom`, but this will be processed regardless of + # the chosen variant. + # + # Comment or delete section below if necessary. + # ---------------------------------------------------------------------- + #custom: + # - name: SAMPLE_PROPERTY + # value: Sample value + # destination: project diff --git a/Tests/VariantsCoreTests/VariantsFileFactoryTests.swift b/Tests/VariantsCoreTests/VariantsFileFactoryTests.swift index 8c368a0f..91b112a5 100644 --- a/Tests/VariantsCoreTests/VariantsFileFactoryTests.swift +++ b/Tests/VariantsCoreTests/VariantsFileFactoryTests.swift @@ -26,8 +26,19 @@ class VariantsFileFactoryTests: XCTestCase { } return infoDictionary }() + + // MARK: - ConfigurationValueKey + /// Custom configuration values coming from variants.yml as enum cases + public enum ConfigurationValueKey: String { + + case PROPERTY_A + case PROPERTY_B + } + static func configurationValue(for key: ConfigurationValueKey) -> Any? { + return Self.configuration[key.rawValue] + } + } - """ private let defaultVariant = try? iOSVariant( @@ -36,16 +47,24 @@ class VariantsFileFactoryTests: XCTestCase { versionNumber: 99, appIcon: nil, appName: nil, - storeDestination: "testFlight", - custom: [CustomProperty(name: "PROPERTY_A", value: "VALUE_A", destination: .project), - CustomProperty(name: "PROPERTY_B", value: "VALUE_B", env: true, destination: .project)], + storeDestination: "testflight", idSuffix: nil, bundleID: nil, - variantSigning: nil, - globalSigning: iOSSigning(teamName: "", teamID: "", exportMethod: .appstore, matchURL: ""), + globalCustomProperties: nil, + variantCustomProperties: [ + CustomProperty(name: "PROPERTY_A", value: "VALUE_A", destination: .project), + CustomProperty(name: "PROPERTY_B", value: "VALUE_B", destination: .project)], + globalSigning: iOSSigning(teamName: "", + teamID: "", + exportMethod: .appstore, + matchURL: "", + style: .manual, + autoDetectSigningIdentity: true), + debugSigning: nil, + releaseSigning: nil, globalPostSwitchScript: "echo global", variantPostSwitchScript: "echo variant") - + func testRender_noSecrets() { guard let configFile = Bundle(for: type(of: self)) .path(forResource: "Resources/ios/sample", ofType: "xcconfig") else { return } @@ -60,6 +79,9 @@ class VariantsFileFactoryTests: XCTestCase { let variantsFilePath = Bundle(for: type(of: self)).path(forResource: "Resources/ios/Variants", ofType: "swift") XCTAssertNotNil(variantsFilePath) + + // Note: We are skipping the check for the file content as multiple tests edit the same file which leads to CI failure + // We need to refactor the test to write the file in a way it won't break when running multiple tests // guard let variantsFile = variantsFilePath else { return } // XCTAssertEqual(try String(contentsOfFile: variantsFile), variantsSwiftContent) } diff --git a/Tests/VariantsCoreTests/XcodeProjFactoryTests.swift b/Tests/VariantsCoreTests/XcodeProjFactoryTests.swift index 123d60d8..6e8fc476 100644 --- a/Tests/VariantsCoreTests/XcodeProjFactoryTests.swift +++ b/Tests/VariantsCoreTests/XcodeProjFactoryTests.swift @@ -31,26 +31,26 @@ class XcodeProjFactoryTests: XCTestCase { XCTAssertTrue(success) XCTAssertNotNil(path) } - - func testCreateConfiguration() { - let proj = XCConfigFactory(logger: Logger(verbose: true)) - let target = iOSTarget(name: "", app_icon: "", bundleId: "", testTarget: "", - source: .init(path: "", info: "", config: "")) - guard let variant = try? iOSVariant(name: target.name, versionName: "", versionNumber: 0, appIcon: nil, appName: nil, - storeDestination: nil, custom: nil, idSuffix: "", bundleID: nil, variantSigning: nil, - globalSigning: iOSSigning(teamName: "", teamID: "", exportMethod: .appstore, matchURL: ""), - globalPostSwitchScript: nil, variantPostSwitchScript: nil) - else { - return XCTFail("Failed to initialize iOSVariant with provided parameters") - } - XCTAssertNoThrow(try proj.createConfig( - with: ("", target), - variant: variant, - xcodeProj: xcodeProjectPath.description, - configPath: Path(""), - addToXcodeProj: false - )) - } + + // TODO: This test will always fail since the test can't find a .xcodeproj file to update +// func testCreateConfiguration() { +// let proj = XCConfigFactory(logger: Logger(verbose: true)) +// let target = iOSTarget(name: "", app_icon: "", bundleId: "", testTarget: "", +// source: .init(path: "", info: "", config: "")) +// guard let variant = try? iOSVariant( +// name: target.name, versionName: "", versionNumber: 0, appIcon: nil, appName: nil, storeDestination: nil, +// idSuffix: "", bundleID: nil, globalCustomProperties: nil, variantCustomProperties: nil, +// globalSigning: iOSSigning(teamName: "", teamID: "", exportMethod: .appstore, matchURL: ""), +// variantSigning: nil, globalPostSwitchScript: nil, variantPostSwitchScript: nil) +// else { +// return XCTFail("Failed to initialize iOSVariant with provided parameters") +// } +// XCTAssertNoThrow(try proj.createConfig( +// for: target, +// variant: variant, +// xcodeProj: xcodeProjectPath.description, +// configPath: Path(""))) +// } func testApplicationData() { let sut = XcodeProjFactory() diff --git a/Tests/VariantsCoreTests/YamlParserTests.swift b/Tests/VariantsCoreTests/YamlParserTests.swift index 09626e74..48357767 100644 --- a/Tests/VariantsCoreTests/YamlParserTests.swift +++ b/Tests/VariantsCoreTests/YamlParserTests.swift @@ -5,6 +5,8 @@ // Created by Arthur Alves // +// swiftlint:disable type_body_length +// swiftlint:disable function_body_length // swiftlint:disable file_length import XCTest @@ -74,7 +76,7 @@ class YamlParserTests: XCTestCase { } } } - // swiftlint:disable function_body_length + func testExtractConfiguration_valid_iOS() { let parser = YamlParser() do { @@ -86,9 +88,8 @@ class YamlParserTests: XCTestCase { XCTAssertNotNil(configuration.ios) if let iosConfiguration = configuration.ios { - XCTAssertEqual(iosConfiguration.targets.count, 1) - XCTAssertEqual(iosConfiguration.targets.first?.value.name, "FrankBank") - XCTAssertEqual(iosConfiguration.targets.first?.value.bundleId, "com.backbase.frank.ios") + XCTAssertEqual(iosConfiguration.target.name, "FrankBank") + XCTAssertEqual(iosConfiguration.target.bundleId, "com.backbase.frank.ios") XCTAssertEqual(iosConfiguration.variants.count, 3) XCTAssertTrue(iosConfiguration.variants.map(\.name).contains("default")) XCTAssertTrue(iosConfiguration.variants.map(\.name).contains("BETA")) @@ -104,22 +105,24 @@ class YamlParserTests: XCTestCase { iOSTarget(name: "FrankBank", app_icon: "AppIcon", bundleId: "com.backbase.frank.ios", testTarget: "FrankBankTests", source: source) ) - XCTAssertEqual(firstVariantDefaultValues?.count, 7) + XCTAssertEqual(firstVariantDefaultValues?.count, 8) XCTAssertEqual(firstVariantDefaultValues?[0].key, "SAMPLE_CONFIG") XCTAssertEqual(firstVariantDefaultValues?[0].value, "Production Value") - XCTAssertEqual(firstVariantDefaultValues?[1].key, "V_APP_ICON") - XCTAssertEqual(firstVariantDefaultValues?[1].value, "AppIcon") - XCTAssertEqual(firstVariantDefaultValues?[2].key, "V_APP_NAME") - XCTAssertEqual(firstVariantDefaultValues?[2].value, "FrankBank") - XCTAssertEqual(firstVariantDefaultValues?[3].key, "V_BUNDLE_ID") - XCTAssertEqual(firstVariantDefaultValues?[3].value, "com.backbase.frank.ios") - XCTAssertEqual(firstVariantDefaultValues?[4].key, "V_MATCH_PROFILE") - XCTAssertEqual(firstVariantDefaultValues?[4].value, "match AppStore com.backbase.frank.ios") - XCTAssertEqual(firstVariantDefaultValues?[5].key, "V_VERSION_NAME") - XCTAssertEqual(firstVariantDefaultValues?[5].value, "0.0.1") - XCTAssertEqual(firstVariantDefaultValues?[6].key, "V_VERSION_NUMBER") - XCTAssertEqual(firstVariantDefaultValues?[6].value, "1") - + XCTAssertEqual(firstVariantDefaultValues?[1].key, "SAMPLE_GLOBAL") + XCTAssertEqual(firstVariantDefaultValues?[1].value, "GLOBAL Value iOS") + XCTAssertEqual(firstVariantDefaultValues?[2].key, "V_APP_ICON") + XCTAssertEqual(firstVariantDefaultValues?[2].value, "AppIcon") + XCTAssertEqual(firstVariantDefaultValues?[3].key, "V_APP_NAME") + XCTAssertEqual(firstVariantDefaultValues?[3].value, "FrankBank") + XCTAssertEqual(firstVariantDefaultValues?[4].key, "V_BUNDLE_ID") + XCTAssertEqual(firstVariantDefaultValues?[4].value, "com.backbase.frank.ios") + XCTAssertEqual(firstVariantDefaultValues?[5].key, "V_MATCH_PROFILE") + XCTAssertEqual(firstVariantDefaultValues?[5].value, "match AppStore com.backbase.frank.ios") + XCTAssertEqual(firstVariantDefaultValues?[6].key, "V_VERSION_NAME") + XCTAssertEqual(firstVariantDefaultValues?[6].value, "0.0.1") + XCTAssertEqual(firstVariantDefaultValues?[7].key, "V_VERSION_NUMBER") + XCTAssertEqual(firstVariantDefaultValues?[7].value, "1") + // MARK: - iOS Global Properties let customGlobalConfig = configuration.ios? @@ -135,39 +138,65 @@ class YamlParserTests: XCTestCase { XCTAssertNotNil(customConfigBeta) assertCustom(customConfigBeta!, value: "BETA Value", destination: .fastlane) - // MARK: - iOS Signing Configuration - - let defaultMatchConfiguration = firstVariant?.signing - XCTAssertNotNil(defaultMatchConfiguration) - XCTAssertEqual(defaultMatchConfiguration?.teamName, "BACKBASE EUROPE B.V.") - XCTAssertEqual(defaultMatchConfiguration?.teamID, "AB123456CD") - XCTAssertEqual(defaultMatchConfiguration?.matchURL, "git@github.com:sample/match.git") - XCTAssertEqual(defaultMatchConfiguration?.exportMethod, .appstore) - - let betaMatchConfiguration = configuration.ios? + // MARK: - iOS Signing Configuration for debug + + let defaultMatchDebugConfiguration = firstVariant?.debugSigning + XCTAssertNotNil(defaultMatchDebugConfiguration) + XCTAssertEqual(defaultMatchDebugConfiguration?.teamName, "BACKBASE EUROPE B.V.") + XCTAssertEqual(defaultMatchDebugConfiguration?.teamID, "AB123456CD") + XCTAssertEqual(defaultMatchDebugConfiguration?.matchURL, "git@github.com:sample/match.git") + XCTAssertEqual(defaultMatchDebugConfiguration?.exportMethod, .appstore) + + let betaMatchDebugConfiguration = configuration.ios? .variants.first(where: { $0.name == "BETA" })? - .signing - XCTAssertNotNil(betaMatchConfiguration) - XCTAssertEqual(betaMatchConfiguration?.teamName, "iPhone Distribution: BACKBASE EUROPE B.V.") - XCTAssertEqual(betaMatchConfiguration?.teamID, "AB123456CD") - XCTAssertNil(betaMatchConfiguration?.matchURL) - XCTAssertEqual(betaMatchConfiguration?.exportMethod, .enterprise) - - let stagingMatchConfiguration = configuration.ios? + .debugSigning + XCTAssertNotNil(betaMatchDebugConfiguration) + XCTAssertEqual(betaMatchDebugConfiguration?.teamName, "iPhone Distribution: BACKBASE EUROPE B.V.") + XCTAssertEqual(betaMatchDebugConfiguration?.teamID, "AB123456CD") + XCTAssertNil(betaMatchDebugConfiguration?.matchURL) + XCTAssertEqual(betaMatchDebugConfiguration?.exportMethod, .enterprise) + + let stagingMatchDebugConfiguration = configuration.ios? .variants.first(where: { $0.name == "STG" })? - .signing - XCTAssertNotNil(stagingMatchConfiguration) - XCTAssertEqual(stagingMatchConfiguration?.teamName, "iPhone Distribution: BACKBASE EUROPE B.V.") - XCTAssertEqual(stagingMatchConfiguration?.teamID, "AB123456CD") - XCTAssertEqual(stagingMatchConfiguration?.matchURL, "git@github.com:sample/enterprise-match.git") - XCTAssertEqual(stagingMatchConfiguration?.exportMethod, .enterprise) - + .debugSigning + XCTAssertNotNil(stagingMatchDebugConfiguration) + XCTAssertEqual(stagingMatchDebugConfiguration?.teamName, "iPhone Distribution: BACKBASE EUROPE B.V.") + XCTAssertEqual(stagingMatchDebugConfiguration?.teamID, "AB123456CD") + XCTAssertEqual(stagingMatchDebugConfiguration?.matchURL, "git@github.com:sample/enterprise-match.git") + XCTAssertEqual(stagingMatchDebugConfiguration?.exportMethod, .enterprise) + + // MARK: - iOS Signing Configuration for release + + let defaultMatchReleaseConfiguration = firstVariant?.releaseSigning + XCTAssertNotNil(defaultMatchReleaseConfiguration) + XCTAssertEqual(defaultMatchReleaseConfiguration?.teamName, "BACKBASE EUROPE B.V.") + XCTAssertEqual(defaultMatchReleaseConfiguration?.teamID, "AB123456CD") + XCTAssertEqual(defaultMatchReleaseConfiguration?.matchURL, "git@github.com:sample/match.git") + XCTAssertEqual(defaultMatchReleaseConfiguration?.exportMethod, .appstore) + + let betaMatchReleaseConfiguration = configuration.ios? + .variants.first(where: { $0.name == "BETA" })? + .releaseSigning + XCTAssertNotNil(betaMatchReleaseConfiguration) + XCTAssertEqual(betaMatchReleaseConfiguration?.teamName, "iPhone Distribution: BACKBASE EUROPE B.V.") + XCTAssertEqual(betaMatchReleaseConfiguration?.teamID, "AB123456CD") + XCTAssertNil(betaMatchReleaseConfiguration?.matchURL) + XCTAssertEqual(betaMatchReleaseConfiguration?.exportMethod, .enterprise) + + let stagingMatchReleaseConfiguration = configuration.ios? + .variants.first(where: { $0.name == "STG" })? + .releaseSigning + XCTAssertNotNil(stagingMatchReleaseConfiguration) + XCTAssertEqual(stagingMatchReleaseConfiguration?.teamName, "iPhone Distribution: BACKBASE EUROPE B.V.") + XCTAssertEqual(stagingMatchReleaseConfiguration?.teamID, "AB123456CD") + XCTAssertEqual(stagingMatchReleaseConfiguration?.matchURL, "git@github.com:sample/enterprise-match.git") + XCTAssertEqual(stagingMatchReleaseConfiguration?.exportMethod, .enterprise) } catch { dump(error) XCTAssertTrue(((error as? DecodingError) == nil)) } } - // swiftlint:enable function_body_length + func testExtractConfiguration_valid_android() { let parser = YamlParser() do { @@ -291,4 +320,7 @@ class YamlParserTests: XCTestCase { testStoreDestination_iOS) ] } + +// swiftlint:enable type_body_length +// swiftlint:enable function_body_length // swiftlint:enable file_length diff --git a/Tests/VariantsCoreTests/iOSProjectTests.swift b/Tests/VariantsCoreTests/iOSProjectTests.swift index 652ec51b..1f6cd177 100644 --- a/Tests/VariantsCoreTests/iOSProjectTests.swift +++ b/Tests/VariantsCoreTests/iOSProjectTests.swift @@ -5,11 +5,12 @@ // Created by Arthur Alves // +// swiftlint:disable type_name + import XCTest import PathKit import ArgumentParser @testable import VariantsCore -// swiftlint:disable type_name class iOSProjectTests: XCTestCase { let specHelperMock = SpecHelperMock( @@ -20,7 +21,7 @@ class iOSProjectTests: XCTestCase { ) func testProject_initialize() { - let xcFactoryMock = MockXCcodeConfigFactory(logLevel: true) + let xcFactoryMock = MockXCodeConfigFactory(logLevel: true) let parametersFactoryMock = MockFastlaneFactory() let project = iOSProject( @@ -37,7 +38,7 @@ class iOSProjectTests: XCTestCase { } func testProject_setup() { - let xcFactoryMock = MockXCcodeConfigFactory(logLevel: true) + let xcFactoryMock = MockXCodeConfigFactory(logLevel: true) let parametersFactoryMock = MockFastlaneFactory() let project = iOSProject( @@ -79,7 +80,7 @@ class iOSProjectTests: XCTestCase { } func testProject_list() { - let xcFactoryMock = MockXCcodeConfigFactory(logLevel: true) + let xcFactoryMock = MockXCodeConfigFactory(logLevel: true) let parametersFactoryMock = MockFastlaneFactory() let project = iOSProject( specHelper: specHelperMock, @@ -98,7 +99,7 @@ class iOSProjectTests: XCTestCase { } func testProject_switch() { - let xcFactoryMock = MockXCcodeConfigFactory(logLevel: true) + let xcFactoryMock = MockXCodeConfigFactory(logLevel: true) let parametersFactoryMock = MockFastlaneFactory() let project = iOSProject( @@ -152,7 +153,7 @@ class iOSProjectTests: XCTestCase { } func testProject_setup_missingiOSConfiguration() { - let xcFactoryMock = MockXCcodeConfigFactory(logLevel: true) + let xcFactoryMock = MockXCodeConfigFactory(logLevel: true) let parametersFactoryMock = MockFastlaneFactory() let project = iOSProject( @@ -175,7 +176,7 @@ class iOSProjectTests: XCTestCase { } func testProject_setup_fail() { - let xcFactoryMock = MockXCcodeConfigFactory(logLevel: true) + let xcFactoryMock = MockXCodeConfigFactory(logLevel: true) let parametersFactoryMock = MockFastlaneFactory() let project = iOSProject( diff --git a/Tests/VariantsCoreTests/iOSSigningTests.swift b/Tests/VariantsCoreTests/iOSSigningTests.swift index 0812aa15..a94d1561 100644 --- a/Tests/VariantsCoreTests/iOSSigningTests.swift +++ b/Tests/VariantsCoreTests/iOSSigningTests.swift @@ -5,23 +5,35 @@ // Created by Roman Huti // +// swiftlint:disable type_name +// swiftlint:disable line_length + import XCTest @testable import VariantsCore -// swiftlint:disable type_name - final class iOSSigningTests: XCTestCase { + private func makeUnnamedVariant(signing: iOSSigning?, debugSigning: iOSSigning?, releaseSigning: iOSSigning?) -> UnnamediOSVariant { + return UnnamediOSVariant( + versionName: "1", versionNumber: 1, appIcon: nil, appName: "AppName", idSuffix: "test", bundleID: nil, + signing: signing, debugSigning: debugSigning, releaseSigning: releaseSigning, + custom: nil, storeDestination: "appstore", postSwitchScript: nil) + } + func testMergeValidSignings() throws { let signing = iOSSigning(teamName: "team", teamID: nil, exportMethod: .appstore, - matchURL: "url") + matchURL: "url", + style: .manual, + autoDetectSigningIdentity: true) let signing1 = iOSSigning(teamName: nil, teamID: "new id", exportMethod: .development, - matchURL: nil) - + matchURL: nil, + style: .manual, + autoDetectSigningIdentity: true) + do { let result = try signing ~ signing1 XCTAssertEqual(result.teamName, "team") @@ -37,11 +49,15 @@ final class iOSSigningTests: XCTestCase { let signing = iOSSigning(teamName: nil, teamID: nil, exportMethod: .appstore, - matchURL: "url") + matchURL: "url", + style: .manual, + autoDetectSigningIdentity: true) let signing1 = iOSSigning(teamName: nil, teamID: "new id", exportMethod: .development, - matchURL: "new url") + matchURL: "new url", + style: .manual, + autoDetectSigningIdentity: true) let expectedError = RuntimeError(""" Missing: 'signing.team_name' At least one variant doesn't contain 'signing.team_name' in its configuration. @@ -61,11 +77,15 @@ final class iOSSigningTests: XCTestCase { let signing = iOSSigning(teamName: nil, teamID: nil, exportMethod: .appstore, - matchURL: "url") + matchURL: "url", + style: .manual, + autoDetectSigningIdentity: true) let signing1 = iOSSigning(teamName: "Name", teamID: nil, exportMethod: .development, - matchURL: "new url") + matchURL: "new url", + style: .manual, + autoDetectSigningIdentity: true) let expectedError = RuntimeError(""" Missing: 'signing.team_id' At least one variant doesn't contain 'signing.team_id' in its configuration. @@ -85,8 +105,10 @@ final class iOSSigningTests: XCTestCase { let signing = iOSSigning(teamName: "Name", teamID: nil, exportMethod: .enterprise, - matchURL: "url") - + matchURL: "url", + style: .manual, + autoDetectSigningIdentity: true) + let expected = [CustomProperty(name: "TEAMNAME", value: "NAME", destination: .fastlane), CustomProperty(name: "EXPORTMETHOD", value: "match InHouse", destination: .fastlane), CustomProperty(name: "MATCHURL", value: "url", destination: .fastlane)] @@ -95,16 +117,91 @@ final class iOSSigningTests: XCTestCase { } func testExportMethodPrefixes() { - let dev: iOSSigning.`Type` = .development, - appstore: iOSSigning.`Type` = .appstore, - enterprise: iOSSigning.`Type` = .enterprise, - adhoc: iOSSigning.`Type` = .adhoc + let dev: iOSSigning.ExportMethod = .development, + appstore: iOSSigning.ExportMethod = .appstore, + enterprise: iOSSigning.ExportMethod = .enterprise, + adhoc: iOSSigning.ExportMethod = .adhoc XCTAssertEqual(dev.prefix, "match Development") XCTAssertEqual(appstore.prefix, "match AppStore") XCTAssertEqual(enterprise.prefix, "match InHouse") XCTAssertEqual(adhoc.prefix, "match AdHoc") } + func testOnlyGlobalSigning() { + let globalSigning = iOSSigning(teamName: "global team name", teamID: "global_team_id", exportMethod: .appstore, matchURL: "global match url", style: .manual, autoDetectSigningIdentity: true) + let unnamedVariant = makeUnnamedVariant(signing: nil, debugSigning: nil, releaseSigning: nil) + guard + let variant = try? iOSVariant(from: unnamedVariant, name: "", globalCustomProperties: nil, globalSigning: globalSigning, globalPostSwitchScript: nil) + else { return XCTFail("Failed to generate variants") } + + XCTAssertEqual(variant.debugSigning, globalSigning) + XCTAssertEqual(variant.releaseSigning, globalSigning) + } + + func testGlobalAndVariantSigning() { + let globalSigning = iOSSigning(teamName: "global team name", teamID: "global_team_id", exportMethod: .appstore, matchURL: "global match url", style: .manual, autoDetectSigningIdentity: true) + let variantSigning = iOSSigning(teamName: "variant team name", teamID: "variant_team_id", exportMethod: .appstore, matchURL: "variant match url", style: .manual, autoDetectSigningIdentity: false) + let unnamedVariant = makeUnnamedVariant(signing: variantSigning, debugSigning: nil, releaseSigning: nil) + guard + let variant = try? iOSVariant(from: unnamedVariant, name: "", globalCustomProperties: nil, globalSigning: globalSigning, globalPostSwitchScript: nil) + else { return XCTFail("Failed to generate variants") } + + XCTAssertEqual(variant.debugSigning, variantSigning) + XCTAssertEqual(variant.releaseSigning, variantSigning) + } + + func testGlobalAndVariantReleaseSigning() { + let globalSigning = iOSSigning(teamName: "global team name", teamID: "global_team_id", exportMethod: .appstore, matchURL: "global match url", style: .manual, autoDetectSigningIdentity: true) + let variantReleaseSigning = iOSSigning(teamName: "variant team name", teamID: "variant_team_id", exportMethod: .appstore, matchURL: "variant match url", style: .manual, autoDetectSigningIdentity: false) + let unnamedVariant = makeUnnamedVariant(signing: nil, debugSigning: nil, releaseSigning: variantReleaseSigning) + guard + let variant = try? iOSVariant(from: unnamedVariant, name: "", globalCustomProperties: nil, globalSigning: globalSigning, globalPostSwitchScript: nil) + else { return XCTFail("Failed to generate variants") } + + XCTAssertEqual(variant.debugSigning, globalSigning) + XCTAssertEqual(variant.releaseSigning, variantReleaseSigning) + } + + func testGlobalAndVariantDebugSigning() { + let globalSigning = iOSSigning(teamName: "global team name", teamID: "global_team_id", exportMethod: .appstore, matchURL: "global match url", style: .manual, autoDetectSigningIdentity: true) + let variantDebugSigning = iOSSigning(teamName: "variant team name", teamID: "variant_team_id", exportMethod: .appstore, matchURL: "variant match url", style: .manual, autoDetectSigningIdentity: false) + let unnamedVariant = makeUnnamedVariant(signing: nil, debugSigning: variantDebugSigning, releaseSigning: nil) + guard + let variant = try? iOSVariant(from: unnamedVariant, name: "", globalCustomProperties: nil, globalSigning: globalSigning, globalPostSwitchScript: nil) + else { return XCTFail("Failed to generate variants") } + + XCTAssertEqual(variant.debugSigning, variantDebugSigning) + XCTAssertEqual(variant.releaseSigning, globalSigning) + } + + func testGlobalAndVariantReleaseDebugSigning() { + let globalSigning = iOSSigning(teamName: "global team name", teamID: "global_team_id", exportMethod: .appstore, matchURL: "global match url", style: .manual, autoDetectSigningIdentity: true) + let variantDebugSigning = iOSSigning(teamName: "variant debug team name", teamID: "variant_debug_team_id", + exportMethod: .appstore, matchURL: "variant match url", style: .manual, autoDetectSigningIdentity: true) + let variantReleaseSigning = iOSSigning(teamName: "variant release team name", teamID: "variant_release_team_id", + exportMethod: .appstore, matchURL: "variant match url", style: .manual, autoDetectSigningIdentity: false) + let unnamedVariant = makeUnnamedVariant(signing: nil, debugSigning: variantDebugSigning, releaseSigning: variantReleaseSigning) + guard + let variant = try? iOSVariant(from: unnamedVariant, name: "", globalCustomProperties: nil, globalSigning: globalSigning, globalPostSwitchScript: nil) + else { return XCTFail("Failed to generate variants") } + + XCTAssertEqual(variant.debugSigning, variantDebugSigning) + XCTAssertEqual(variant.releaseSigning, variantReleaseSigning) + } + + func testGlobalAndVariantSigningAndDebugSigning() { + let globalSigning = iOSSigning(teamName: "global team name", teamID: "global_team_id", exportMethod: .appstore, matchURL: "global match url", style: .manual, autoDetectSigningIdentity: true) + let variantSigning = iOSSigning(teamName: "variant team name", teamID: "variant_team_id", exportMethod: .appstore, matchURL: "variant match url", style: .manual, autoDetectSigningIdentity: true) + let variantDebugSigning = iOSSigning(teamName: "variant debug team name", teamID: "variant_debug_team_id", exportMethod: .appstore, matchURL: "variant match url", style: .manual, autoDetectSigningIdentity: true) + let unnamedVariant = makeUnnamedVariant(signing: variantSigning, debugSigning: variantDebugSigning, releaseSigning: nil) + guard + let variant = try? iOSVariant(from: unnamedVariant, name: "", globalCustomProperties: nil, globalSigning: globalSigning, globalPostSwitchScript: nil) + else { return XCTFail("Failed to generate variants") } + + XCTAssertEqual(variant.debugSigning, variantDebugSigning) + XCTAssertEqual(variant.releaseSigning, variantSigning) + } } // swiftlint:enable type_name +// swiftlint:enable line_length diff --git a/Tests/VariantsCoreTests/iOSTargetExtensionTests.swift b/Tests/VariantsCoreTests/iOSTargetExtensionTests.swift new file mode 100644 index 00000000..23dc9a48 --- /dev/null +++ b/Tests/VariantsCoreTests/iOSTargetExtensionTests.swift @@ -0,0 +1,81 @@ +// +// Variants +// +// Copyright (c) Backbase B.V. - https://www.backbase.com +// Created by Gabriel Rodrigues Minucci on 27/01/2025. +// + +// swiftlint:disable line_length +// swiftlint:disable type_name + +import XCTest +@testable import VariantsCore + +class iOSTargetExtensionTests: XCTestCase { + private let validSigning = iOSSigning(teamName: "Signing Team Name", teamID: "AB12345CD", exportMethod: .appstore, matchURL: "git@github.com:sample/match.git", style: .manual, autoDetectSigningIdentity: true) + private let target = iOSTarget(name: "Target Name", app_icon: "AppIcon", bundleId: "com.Company.ValidName", testTarget: "ValidNameTests", source: iOSSource(path: "", info: "", config: "")) + + func testTargetExtensionCreationWithBundleSuffix() { + guard let variant = try? iOSVariant( + name: name, versionName: "1.0.0", versionNumber: 0, appIcon: nil, appName: nil, storeDestination: "appStore", + idSuffix: "beta", bundleID: nil, globalCustomProperties: nil, variantCustomProperties: nil, + globalSigning: validSigning, debugSigning: nil, releaseSigning: nil, globalPostSwitchScript: nil, variantPostSwitchScript: nil) + else { + return XCTFail("Failed to initialize iOSVariant with provided parameters") + } + + let extensionsJsonString = """ + {"name": "TestExtension", "bundle_suffix": "TestExtension", "signed": true} + """ + guard let targetExtension = try? JSONDecoder().decode(iOSExtension.self, from: Data(extensionsJsonString.utf8)) + else { return XCTFail("Failed to decode JSON for extensions data") } + + XCTAssertEqual(targetExtension.name, "TestExtension") + XCTAssertEqual(targetExtension.signed, true) + XCTAssertEqual(targetExtension.bundleNamingOption, .suffix("TestExtension")) + + let generatedBundleForTarget = targetExtension.makeBundleID(variant: variant, target: target) + XCTAssertEqual(generatedBundleForTarget, "com.Company.ValidName.beta.TestExtension") + } + + func testTargetExtensionCreationWithBundleID() { + guard let variant = try? iOSVariant( + name: name, versionName: "1.0.0", versionNumber: 0, appIcon: nil, appName: nil, storeDestination: "appStore", + idSuffix: "beta", bundleID: nil, globalCustomProperties: nil, variantCustomProperties: nil, + globalSigning: validSigning, debugSigning: nil, releaseSigning: nil, globalPostSwitchScript: nil, variantPostSwitchScript: nil) + else { + return XCTFail("Failed to initialize iOSVariant with provided parameters") + } + + let extensionsJsonString = """ + {"name": "TestExtension", "bundle_id": "com.test.App.TestExtension", "signed": true} + """ + + guard let targetExtension = try? JSONDecoder().decode(iOSExtension.self, from: Data(extensionsJsonString.utf8)) + else { return XCTFail("Failed to decode JSON for extensions data") } + + XCTAssertEqual(targetExtension.name, "TestExtension") + XCTAssertEqual(targetExtension.signed, true) + XCTAssertEqual(targetExtension.bundleNamingOption, .explicit("com.test.App.TestExtension")) + + let generatedBundleForTarget = targetExtension.makeBundleID(variant: variant, target: target) + XCTAssertEqual(generatedBundleForTarget, "com.test.App.TestExtension") + } + + func testTargetExtensionCreationWithBundleIDAndBundleSuffix() { + let extensionsJsonString = """ + {"name": "TestExtension", "bundle_suffix": "TestExtension", "bundle_id": "com.test.App.TestExtension", "signed": true} + """ + + XCTAssertThrowsError(try JSONDecoder().decode(iOSExtension.self, from: Data(extensionsJsonString.utf8))) + } + + static var allTests = [ + ("testTargetExtensionCreationWithBundleSuffix", testTargetExtensionCreationWithBundleSuffix), + ("testTargetExtensionCreationWithBundleID", testTargetExtensionCreationWithBundleID), + ("testTargetExtensionCreationWithBundleIDAndBundleSuffix", testTargetExtensionCreationWithBundleIDAndBundleSuffix) + ] +} + +// swiftlint:enable line_length +// swiftlint:enable type_name diff --git a/Tests/VariantsCoreTests/iOSVariantTests.swift b/Tests/VariantsCoreTests/iOSVariantTests.swift index 839cedcc..7fd26a55 100644 --- a/Tests/VariantsCoreTests/iOSVariantTests.swift +++ b/Tests/VariantsCoreTests/iOSVariantTests.swift @@ -14,17 +14,17 @@ import XCTest @testable import VariantsCore class iOSVariantTests: XCTestCase { - private let validSigning = iOSSigning(teamName: "Signing Team Name", teamID: "AB12345CD", exportMethod: .appstore, matchURL: "git@github.com:sample/match.git") + private let validSigning = iOSSigning(teamName: "Signing Team Name", teamID: "AB12345CD", exportMethod: .appstore, matchURL: "git@github.com:sample/match.git", style: .manual, autoDetectSigningIdentity: false) private let target = iOSTarget(name: "Target Name", app_icon: "AppIcon", bundleId: "com.Company.ValidName", testTarget: "ValidNameTests", source: iOSSource(path: "", info: "", config: "")) // MARK: - Initializer tests func testiOSVariantInitWithUnnamediOSVariant() { let customProperties = [CustomProperty(name: "Name", value: "Value", destination: .project)] let unnamedVariant = UnnamediOSVariant(versionName: "1.0", versionNumber: 0, appIcon: "app_icon", appName: nil, idSuffix: "beta", bundleID: nil, - signing: validSigning, custom: customProperties, storeDestination: "testflight", postSwitchScript: "echo hello") - + signing: validSigning, debugSigning: nil, releaseSigning: nil, custom: customProperties, storeDestination: "testflight", postSwitchScript: "echo hello") + func makeiOSVariant() throws -> iOSVariant { - try iOSVariant(from: unnamedVariant, name: "beta", globalSigning: nil, globalPostSwitchScript: nil) + try iOSVariant(from: unnamedVariant, name: "beta", globalCustomProperties: nil, globalSigning: nil, globalPostSwitchScript: nil) } XCTAssertNoThrow(try makeiOSVariant()) @@ -43,8 +43,9 @@ class iOSVariantTests: XCTestCase { // MARK: - Default property assigning func testInitNilFallbackToDefaultProperties() { func makeiOSVariant() throws -> iOSVariant { - try iOSVariant(name: "Valid Name", versionName: "1.0.0", versionNumber: 0, appIcon: nil, appName: nil, storeDestination: nil, custom: nil, - idSuffix: "beta", bundleID: nil, variantSigning: nil, globalSigning: validSigning, globalPostSwitchScript: "echo global", variantPostSwitchScript: "echo variant") + try iOSVariant(name: "Valid Name", versionName: "1.0.0", versionNumber: 0, appIcon: nil, appName: nil, storeDestination: nil, idSuffix: "beta", + bundleID: nil, globalCustomProperties: nil, variantCustomProperties: nil, globalSigning: validSigning, debugSigning: nil, releaseSigning: nil, + globalPostSwitchScript: "echo global", variantPostSwitchScript: "echo variant") } XCTAssertNoThrow(try makeiOSVariant()) @@ -56,8 +57,9 @@ class iOSVariantTests: XCTestCase { // MARK: - Computed properties func testGetTitle() { let name = "Variant Name" - guard let variant = try? iOSVariant(name: name, versionName: "1.0.0", versionNumber: 0, appIcon: nil, appName: nil, storeDestination: "appStore", custom: nil, - idSuffix: "beta", bundleID: nil, variantSigning: nil, globalSigning: validSigning, globalPostSwitchScript: "echo global", variantPostSwitchScript: "echo variant") + guard let variant = try? iOSVariant(name: name, versionName: "1.0.0", versionNumber: 0, appIcon: nil, appName: nil, storeDestination: "appStore", + idSuffix: "beta", bundleID: nil, globalCustomProperties: nil, variantCustomProperties: nil, + globalSigning: validSigning, debugSigning: nil, releaseSigning: nil, globalPostSwitchScript: nil, variantPostSwitchScript: nil) else { return XCTFail("Failed to initialize iOSVariant with provided parameters") } @@ -66,8 +68,9 @@ class iOSVariantTests: XCTestCase { func testGetConfigName() { // Default variant - guard let defaultVariant = try? iOSVariant(name: "default", versionName: "1.0.0", versionNumber: 0, appIcon: nil, appName: nil, storeDestination: "appStore", custom: nil, - idSuffix: "beta", bundleID: nil, variantSigning: nil, globalSigning: validSigning, globalPostSwitchScript: "echo global", variantPostSwitchScript: "echo variant") + guard let defaultVariant = try? iOSVariant(name: "default", versionName: "1.0.0", versionNumber: 0, appIcon: nil, appName: nil, storeDestination: "appStore", + idSuffix: "beta", bundleID: nil, globalCustomProperties: nil, variantCustomProperties: nil, + globalSigning: validSigning, debugSigning: nil, releaseSigning: nil, globalPostSwitchScript: nil, variantPostSwitchScript: nil) else { return XCTFail("Failed to initialize iOSVariant with provided parameters") } @@ -75,8 +78,9 @@ class iOSVariantTests: XCTestCase { // Any variant let name = "Variant Name" - guard let anyVariant = try? iOSVariant(name: name, versionName: "1.0.0", versionNumber: 0, appIcon: nil, appName: nil, storeDestination: "appStore", custom: nil, - idSuffix: "beta", bundleID: nil, variantSigning: nil, globalSigning: validSigning, globalPostSwitchScript: "echo global", variantPostSwitchScript: "echo variant") + guard let anyVariant = try? iOSVariant(name: name, versionName: "1.0.0", versionNumber: 0, appIcon: nil, appName: nil, storeDestination: "appStore", + idSuffix: "beta", bundleID: nil, globalCustomProperties: nil, variantCustomProperties: nil, + globalSigning: validSigning, debugSigning: nil, releaseSigning: nil, globalPostSwitchScript: nil, variantPostSwitchScript: nil) else { return XCTFail("Failed to initialize iOSVariant with provided parameters") } @@ -86,7 +90,8 @@ class iOSVariantTests: XCTestCase { func testGetDestinationProperty() { let targetDestination = iOSVariant.Destination.appCenter guard let variant = try? iOSVariant(name: "Valid Name", versionName: "1.0.0", versionNumber: 0, appIcon: nil, appName: nil, storeDestination: targetDestination.rawValue, - custom: nil, idSuffix: "beta", bundleID: nil, variantSigning: nil, globalSigning: validSigning, globalPostSwitchScript: "echo global", variantPostSwitchScript: "echo variant") + idSuffix: "beta", bundleID: nil, globalCustomProperties: nil, variantCustomProperties: nil, + globalSigning: validSigning, debugSigning: nil, releaseSigning: nil, globalPostSwitchScript: nil, variantPostSwitchScript: nil) else { return XCTFail("Failed to initialize iOSVariant with provided parameters") } @@ -101,10 +106,11 @@ class iOSVariantTests: XCTestCase { // MARK: - Post Switch Script tests func testInitiOSVariantsWithVariantPostSwitchScript() { func makeiOSVariant() throws -> iOSVariant { - try iOSVariant(name: "Valid Name", versionName: "1.0.0", versionNumber: 0, appIcon: nil, appName: nil, storeDestination: nil, custom: nil, - idSuffix: "beta", bundleID: nil, variantSigning: nil, globalSigning: validSigning, globalPostSwitchScript: nil, variantPostSwitchScript: "echo variant") + try iOSVariant(name: "Valid Name", versionName: "1.0.0", versionNumber: 0, appIcon: nil, appName: nil, storeDestination: nil, + idSuffix: "beta", bundleID: nil, globalCustomProperties: nil, variantCustomProperties: nil, + globalSigning: validSigning, debugSigning: nil, releaseSigning: nil, globalPostSwitchScript: nil, variantPostSwitchScript: "echo variant") } - + XCTAssertNoThrow(try makeiOSVariant()) let variant = try? makeiOSVariant() @@ -113,8 +119,9 @@ class iOSVariantTests: XCTestCase { func testInitiOSVariantsWithGlobalPostSwitchScript() { func makeiOSVariant() throws -> iOSVariant { - try iOSVariant(name: "Valid Name", versionName: "1.0.0", versionNumber: 0, appIcon: nil, appName: nil, storeDestination: nil, custom: nil, - idSuffix: "beta", bundleID: nil, variantSigning: nil, globalSigning: validSigning, globalPostSwitchScript: "echo global", variantPostSwitchScript: nil) + try iOSVariant(name: "Valid Name", versionName: "1.0.0", versionNumber: 0, appIcon: nil, appName: nil, storeDestination: nil, + idSuffix: "beta", bundleID: nil, globalCustomProperties: nil, variantCustomProperties: nil, + globalSigning: validSigning, debugSigning: nil, releaseSigning: nil, globalPostSwitchScript: "echo global", variantPostSwitchScript: nil) } XCTAssertNoThrow(try makeiOSVariant()) @@ -125,8 +132,9 @@ class iOSVariantTests: XCTestCase { func testInitiOSVariantsWithVariantAndGlobalPostSwitchScript() { func makeiOSVariant() throws -> iOSVariant { - try iOSVariant(name: "Valid Name", versionName: "1.0.0", versionNumber: 0, appIcon: nil, appName: nil, storeDestination: nil, custom: nil, - idSuffix: "beta", bundleID: nil, variantSigning: nil, globalSigning: validSigning, globalPostSwitchScript: "echo global", variantPostSwitchScript: "echo variant") + try iOSVariant(name: "Valid Name", versionName: "1.0.0", versionNumber: 0, appIcon: nil, appName: nil, storeDestination: nil, + idSuffix: "beta", bundleID: nil, globalCustomProperties: nil, variantCustomProperties: nil, + globalSigning: validSigning, debugSigning: nil, releaseSigning: nil, globalPostSwitchScript: "echo global", variantPostSwitchScript: "echo variant") } XCTAssertNoThrow(try makeiOSVariant()) @@ -137,8 +145,9 @@ class iOSVariantTests: XCTestCase { func testInitiOSVariantsWithNoPostSwitchScript() { func makeiOSVariant() throws -> iOSVariant { - try iOSVariant(name: "Valid Name", versionName: "1.0.0", versionNumber: 0, appIcon: nil, appName: nil, storeDestination: nil, custom: nil, - idSuffix: "beta", bundleID: nil, variantSigning: nil, globalSigning: validSigning, globalPostSwitchScript: nil, variantPostSwitchScript: nil) + try iOSVariant(name: "Valid Name", versionName: "1.0.0", versionNumber: 0, appIcon: nil, appName: nil, storeDestination: nil, + idSuffix: "beta", bundleID: nil, globalCustomProperties: nil, variantCustomProperties: nil, + globalSigning: validSigning, debugSigning: nil, releaseSigning: nil, globalPostSwitchScript: nil, variantPostSwitchScript: nil) } XCTAssertNoThrow(try makeiOSVariant()) @@ -151,36 +160,14 @@ class iOSVariantTests: XCTestCase { func testInitiOSVariantWithIDSuffixOrBundleID() { // Only ID Suffix - XCTAssertNoThrow(try iOSVariant( - name: "Valid Name", - versionName: "1.0.0", - versionNumber: 0, - appIcon: nil, - appName: nil, - storeDestination: "appStore", - custom: nil, - idSuffix: "beta", - bundleID: nil, - variantSigning: nil, - globalSigning: validSigning, - globalPostSwitchScript: "echo global", - variantPostSwitchScript: "echo variant")) - + XCTAssertNoThrow(try iOSVariant(name: "Valid Name", versionName: "1.0.0", versionNumber: 0, appIcon: nil, appName: nil, storeDestination: "appStore", + idSuffix: "beta", bundleID: nil, globalCustomProperties: nil, variantCustomProperties: nil, + globalSigning: validSigning, debugSigning: nil, releaseSigning: nil, globalPostSwitchScript: nil, variantPostSwitchScript: nil)) + // Only Bundle ID - XCTAssertNoThrow(try iOSVariant( - name: "Valid Name", - versionName: "1.0.0", - versionNumber: 0, - appIcon: nil, - appName: nil, - storeDestination: "appStore", - custom: nil, - idSuffix: nil, - bundleID: "com.company.customBundle", - variantSigning: nil, - globalSigning: validSigning, - globalPostSwitchScript: "echo global", - variantPostSwitchScript: "echo variant")) + XCTAssertNoThrow(try iOSVariant(name: "Valid Name", versionName: "1.0.0", versionNumber: 0, appIcon: nil, appName: nil, storeDestination: "appStore", + idSuffix: nil, bundleID: "com.company.customBundle", globalCustomProperties: nil, variantCustomProperties: nil, + globalSigning: validSigning, debugSigning: nil, releaseSigning: nil, globalPostSwitchScript: nil, variantPostSwitchScript: nil)) } func testInitWithIDSuffixAndBundleID() { @@ -192,8 +179,9 @@ class iOSVariantTests: XCTestCase { ) func makeiOSVariant() throws -> iOSVariant { - try iOSVariant(name: "Valid Name", versionName: "1.0.0", versionNumber: 0, appIcon: nil, appName: nil, storeDestination: "appStore", custom: nil, - idSuffix: "beta", bundleID: "com.company.customBundle", variantSigning: nil, globalSigning: validSigning, globalPostSwitchScript: "echo global", variantPostSwitchScript: "echo variant") + try iOSVariant(name: "Valid Name", versionName: "1.0.0", versionNumber: 0, appIcon: nil, appName: nil, storeDestination: "appStore", + idSuffix: "beta", bundleID: "com.company.customBundle", globalCustomProperties: nil, variantCustomProperties: nil, + globalSigning: validSigning, debugSigning: nil, releaseSigning: nil, globalPostSwitchScript: nil, variantPostSwitchScript: nil) } XCTAssertThrowsError(try makeiOSVariant(), "ID Suffix and Bundle ID can't be configured at same time in the same variant") { error in @@ -210,8 +198,9 @@ class iOSVariantTests: XCTestCase { ) func makeiOSVariant() throws -> iOSVariant { - try iOSVariant(name: "Valid Name", versionName: "1.0.0", versionNumber: 0, appIcon: nil, appName: nil, storeDestination: "appStore", custom: nil, - idSuffix: nil, bundleID: nil, variantSigning: nil, globalSigning: validSigning, globalPostSwitchScript: "echo global", variantPostSwitchScript: "echo variant") + try iOSVariant(name: "Valid Name", versionName: "1.0.0", versionNumber: 0, appIcon: nil, appName: nil, storeDestination: "appStore", + idSuffix: nil, bundleID: nil, globalCustomProperties: nil, variantCustomProperties: nil, + globalSigning: validSigning, debugSigning: nil, releaseSigning: nil, globalPostSwitchScript: nil, variantPostSwitchScript: nil) } XCTAssertThrowsError(try makeiOSVariant(), "ID Suffix and Bundle ID can't be configured at same time in the same variant") { error in @@ -221,24 +210,27 @@ class iOSVariantTests: XCTestCase { func testMakeBundleIDForVariant() { // ID Suffix provided - guard let idSuffixVariant = try? iOSVariant(name: "Valid Name", versionName: "1.0.0", versionNumber: 0, appIcon: nil, appName: nil, storeDestination: "appStore", custom: nil, - idSuffix: "beta", bundleID: nil, variantSigning: nil, globalSigning: validSigning, globalPostSwitchScript: "echo global", variantPostSwitchScript: "echo variant") + guard let idSuffixVariant = try? iOSVariant(name: "Valid Name", versionName: "1.0.0", versionNumber: 0, appIcon: nil, appName: nil, storeDestination: "appStore", + idSuffix: "beta", bundleID: nil, globalCustomProperties: nil, variantCustomProperties: nil, + globalSigning: validSigning, debugSigning: nil, releaseSigning: nil, globalPostSwitchScript: nil, variantPostSwitchScript: nil) else { return XCTFail("Failed to initialize iOSVariant with provided parameters") } XCTAssertEqual(idSuffixVariant.makeBundleID(for: target), "com.Company.ValidName.beta") // Bundle ID provided - guard let bundleIDVariant = try? iOSVariant(name: "Valid Name", versionName: "1.0.0", versionNumber: 0, appIcon: nil, appName: nil, storeDestination: "appStore", custom: nil, - idSuffix: nil, bundleID: "com.Overwritten.BundleID", variantSigning: nil, globalSigning: validSigning, globalPostSwitchScript: "echo global", variantPostSwitchScript: "echo variant") + guard let bundleIDVariant = try? iOSVariant(name: "Valid Name", versionName: "1.0.0", versionNumber: 0, appIcon: nil, appName: nil, storeDestination: "appStore", + idSuffix: nil, bundleID: "com.Overwritten.BundleID", globalCustomProperties: nil, variantCustomProperties: nil, + globalSigning: validSigning, debugSigning: nil, releaseSigning: nil, globalPostSwitchScript: nil, variantPostSwitchScript: nil) else { return XCTFail("Failed to initialize iOSVariant with provided parameters") } XCTAssertEqual(bundleIDVariant.makeBundleID(for: target), "com.Overwritten.BundleID") // Default variant - guard let defaultVariant = try? iOSVariant(name: "default", versionName: "1.0.0", versionNumber: 0, appIcon: nil, appName: nil, storeDestination: "appStore", custom: nil, - idSuffix: "beta", bundleID: nil, variantSigning: nil, globalSigning: validSigning, globalPostSwitchScript: "echo global", variantPostSwitchScript: "echo variant") + guard let defaultVariant = try? iOSVariant(name: "default", versionName: "1.0.0", versionNumber: 0, appIcon: nil, appName: nil, storeDestination: "appStore", + idSuffix: "beta", bundleID: nil, globalCustomProperties: nil, variantCustomProperties: nil, + globalSigning: validSigning, debugSigning: nil, releaseSigning: nil, globalPostSwitchScript: nil, variantPostSwitchScript: nil) else { return XCTFail("Failed to initialize iOSVariant with provided parameters") } @@ -249,61 +241,30 @@ class iOSVariantTests: XCTestCase { func testInitWithValidSigningConfiguration() { // Variant and Global signing defined - XCTAssertNoThrow(try iOSVariant( - name: "Valid Name", - versionName: "1.0.0", - versionNumber: 0, - appIcon: nil, - appName: nil, - storeDestination: "appStore", - custom: nil, - idSuffix: "beta", - bundleID: nil, - variantSigning: validSigning, - globalSigning: validSigning, - globalPostSwitchScript: "echo global", - variantPostSwitchScript: "echo variant")) - + XCTAssertNoThrow(try iOSVariant(name: "Valid Name", versionName: "1.0.0", versionNumber: 0, appIcon: nil, appName: nil, + storeDestination: "appStore", idSuffix: "beta", bundleID: nil, globalCustomProperties: nil, variantCustomProperties: nil, + globalSigning: validSigning, debugSigning: validSigning, releaseSigning: validSigning, globalPostSwitchScript: nil, + variantPostSwitchScript: nil)) + // Only variant signing defined - XCTAssertNoThrow(try iOSVariant( - name: "Valid Name", - versionName: "1.0.0", - versionNumber: 0, - appIcon: nil, - appName: nil, - storeDestination: "appStore", - custom: nil, - idSuffix: "beta", - bundleID: nil, - variantSigning: validSigning, - globalSigning: nil, - globalPostSwitchScript: "echo global", - variantPostSwitchScript: "echo variant")) - + XCTAssertNoThrow(try iOSVariant(name: "Valid Name", versionName: "1.0.0", versionNumber: 0, appIcon: nil, appName: nil, + storeDestination: "appStore", idSuffix: "beta", bundleID: nil, globalCustomProperties: nil, variantCustomProperties: nil, + globalSigning: nil, debugSigning: validSigning, releaseSigning: validSigning, globalPostSwitchScript: nil, variantPostSwitchScript: nil)) + // Only global signing defined - XCTAssertNoThrow(try iOSVariant( - name: "Valid Name", - versionName: "1.0.0", - versionNumber: 0, - appIcon: nil, - appName: nil, - storeDestination: "appStore", - custom: nil, - idSuffix: "beta", - bundleID: nil, - variantSigning: nil, - globalSigning: validSigning, - globalPostSwitchScript: "echo global", - variantPostSwitchScript: "echo variant")) + XCTAssertNoThrow(try iOSVariant(name: "Valid Name", versionName: "1.0.0", versionNumber: 0, appIcon: nil, appName: nil, + storeDestination: "appStore", idSuffix: "beta", bundleID: nil, globalCustomProperties: nil, variantCustomProperties: nil, + globalSigning: validSigning, debugSigning: nil, releaseSigning: nil, globalPostSwitchScript: nil, variantPostSwitchScript: nil)) } func testInitWithoutSigningConfiguration() { func makeiOSVariant() throws -> iOSVariant { - try iOSVariant(name: "Valid Name", versionName: "1.0.0", versionNumber: 0, appIcon: nil, appName: nil, storeDestination: "appStore", custom: nil, - idSuffix: "beta", bundleID: nil, variantSigning: nil, globalSigning: nil, globalPostSwitchScript: "echo global", variantPostSwitchScript: "echo variant") + try iOSVariant(name: "Valid Name", versionName: "1.0.0", versionNumber: 0, appIcon: nil, appName: nil, storeDestination: "appStore", + idSuffix: "beta", bundleID: nil, globalCustomProperties: nil, variantCustomProperties: nil, + globalSigning: nil, debugSigning: nil, releaseSigning: nil, globalPostSwitchScript: nil, variantPostSwitchScript: nil) } - XCTAssertNoThrow(try makeiOSVariant()) + XCTAssertThrowsError(try makeiOSVariant()) } func testGetDefaultValuesForTargetWithoutSigning() { @@ -314,9 +275,10 @@ class iOSVariantTests: XCTestCase { "V_VERSION_NAME": "1.0.0", "V_VERSION_NUMBER": "0" ] - let signing = iOSSigning(teamName: "Signing Team Name", teamID: "AB12345CD", exportMethod: .appstore, matchURL: nil) - guard let variant = try? iOSVariant(name: "Beta", versionName: "1.0.0", versionNumber: 0, appIcon: nil, appName: nil, storeDestination: "appStore", custom: nil, - idSuffix: "beta", bundleID: nil, variantSigning: nil, globalSigning: signing, globalPostSwitchScript: "echo global", variantPostSwitchScript: "echo variant") + let signing = iOSSigning(teamName: "Signing Team Name", teamID: "AB12345CD", exportMethod: .appstore, matchURL: nil, style: .manual, autoDetectSigningIdentity: false) + guard let variant = try? iOSVariant(name: "Beta", versionName: "1.0.0", versionNumber: 0, appIcon: nil, appName: nil, storeDestination: "appStore", + idSuffix: "beta", bundleID: nil, globalCustomProperties: nil, variantCustomProperties: nil, + globalSigning: signing, debugSigning: nil, releaseSigning: nil, globalPostSwitchScript: nil, variantPostSwitchScript: nil) else { return XCTFail("Failed to initialize iOSVariant with provided parameters") } @@ -335,10 +297,11 @@ class iOSVariantTests: XCTestCase { "V_VERSION_NAME": "1.0.0", "V_VERSION_NUMBER": "0" ] - let signing = iOSSigning(teamName: "Signing Team Name", teamID: "AB12345CD", exportMethod: .appstore, matchURL: nil) - guard let variant = try? iOSVariant(name: "Beta", versionName: "1.0.0", versionNumber: 0, appIcon: nil, - appName: "App Marketing Name", storeDestination: "appStore", custom: nil, - idSuffix: "beta", bundleID: nil, variantSigning: nil, globalSigning: signing, globalPostSwitchScript: nil, variantPostSwitchScript: nil) + let signing = iOSSigning(teamName: "Signing Team Name", teamID: "AB12345CD", exportMethod: .appstore, matchURL: nil, style: .manual, autoDetectSigningIdentity: false) + guard let variant = try? iOSVariant(name: "Beta", versionName: "1.0.0", versionNumber: 0, appIcon: nil, appName: "App Marketing Name", + storeDestination: "appStore", idSuffix: "beta", bundleID: nil, + globalCustomProperties: nil, variantCustomProperties: nil, globalSigning: signing, debugSigning: nil, releaseSigning: nil, + globalPostSwitchScript: nil, variantPostSwitchScript: nil) else { return XCTFail("Failed to initialize iOSVariant with provided parameters") } @@ -357,10 +320,11 @@ class iOSVariantTests: XCTestCase { "V_VERSION_NAME": "1.0.0", "V_VERSION_NUMBER": "0" ] - let signing = iOSSigning(teamName: "Signing Team Name", teamID: "AB12345CD", exportMethod: .appstore, matchURL: nil) - guard let variant = try? iOSVariant(name: "Beta", versionName: "1.0.0", versionNumber: 0, appIcon: nil, - appName: nil, storeDestination: "appStore", custom: nil, - idSuffix: "beta", bundleID: nil, variantSigning: nil, globalSigning: signing, globalPostSwitchScript: nil, variantPostSwitchScript: nil) + let signing = iOSSigning(teamName: "Signing Team Name", teamID: "AB12345CD", exportMethod: .appstore, matchURL: nil, style: .manual, autoDetectSigningIdentity: false) + guard let variant = try? iOSVariant(name: "Beta", versionName: "1.0.0", versionNumber: 0, appIcon: nil, appName: nil, + storeDestination: "appStore", + idSuffix: "beta", bundleID: nil, globalCustomProperties: nil, variantCustomProperties: nil, + globalSigning: signing, debugSigning: nil, releaseSigning: nil, globalPostSwitchScript: nil, variantPostSwitchScript: nil) else { return XCTFail("Failed to initialize iOSVariant with provided parameters") } @@ -380,8 +344,9 @@ class iOSVariantTests: XCTestCase { "V_VERSION_NAME": "1.0.0", "V_VERSION_NUMBER": "0" ] - guard let variant = try? iOSVariant(name: "Beta", versionName: "1.0.0", versionNumber: 0, appIcon: nil, appName: nil, storeDestination: "appStore", custom: nil, - idSuffix: "beta", bundleID: nil, variantSigning: nil, globalSigning: validSigning, globalPostSwitchScript: "echo global", variantPostSwitchScript: "echo variant") + guard let variant = try? iOSVariant(name: "Beta", versionName: "1.0.0", versionNumber: 0, appIcon: nil, appName: nil, storeDestination: "appStore", + idSuffix: "beta", bundleID: nil, globalCustomProperties: nil, variantCustomProperties: nil, + globalSigning: validSigning, debugSigning: nil, releaseSigning: nil, globalPostSwitchScript: nil, variantPostSwitchScript: nil) else { return XCTFail("Failed to initialize iOSVariant with provided parameters") } @@ -408,7 +373,8 @@ class iOSVariantTests: XCTestCase { CustomProperty(name: "Custom name 2", value: "Custom value 2", env: true, destination: .project), CustomProperty(name: "Custom name 3", value: "Custom value 3", env: false, destination: .fastlane)] guard let variant = try? iOSVariant(name: "Beta", versionName: "1.0.0", versionNumber: 0, appIcon: nil, appName: nil, storeDestination: "appStore", - custom: customProperties, idSuffix: "beta", bundleID: nil, variantSigning: nil, globalSigning: validSigning, globalPostSwitchScript: "echo global", variantPostSwitchScript: "echo variant") + idSuffix: "beta", bundleID: nil, globalCustomProperties: nil, variantCustomProperties: customProperties, + globalSigning: validSigning, debugSigning: nil, releaseSigning: nil, globalPostSwitchScript: nil, variantPostSwitchScript: nil) else { return XCTFail("Failed to initialize iOSVariant with provided parameters") } @@ -421,12 +387,47 @@ class iOSVariantTests: XCTestCase { XCTAssertFalse(defaultValues.contains(where: {$0.key == "Custom name 2"}), "Should not contains this property as it's an environment variable") XCTAssertFalse(defaultValues.contains(where: {$0.key == "Custom name 3"}), "Should not contains this property as it's not a project destination property") } - + + func testGetDefaultValuesWithCustomAndGlobalCustomProperties() { + let customGlobalProperties = [ + CustomProperty(name: "custom_global_property", value: "Custom global property", env: false, destination: .project), + CustomProperty(name: "custom_global_env_property", value: "Custom global env property", env: true, destination: .project), + CustomProperty(name: "custom_global_fastlane_property", value: "Custom global fastlane property", env: false, destination: .fastlane) + ] + let customVariantProperties = [ + CustomProperty(name: "custom_variant_property", value: "Custom variant property", env: false, destination: .project), + CustomProperty(name: "custom_variant_env_property", value: "Custom variant env property", env: true, destination: .project), + CustomProperty(name: "custom_variant_fastlane_property", value: "Custom variant fastlane property", env: false, destination: .fastlane)] + + guard let variant = try? iOSVariant( + name: "Beta", versionName: "1.0.0", versionNumber: 0, appIcon: nil, appName: nil, storeDestination: "appStore", idSuffix: "beta", bundleID: nil, + globalCustomProperties: customGlobalProperties, variantCustomProperties: customVariantProperties, + globalSigning: validSigning, debugSigning: nil, releaseSigning: nil, globalPostSwitchScript: nil, variantPostSwitchScript: nil) + else { + return XCTFail("Failed to initialize iOSVariant with provided parameters") + } + + let defaultValues = variant.getDefaultValues(for: target) + + // Should have both global and variant custom properties + XCTAssertEqual(defaultValues.first(where: { $0.key == "custom_global_property"})?.value, "Custom global property") + XCTAssertEqual(defaultValues.first(where: { $0.key == "custom_variant_property"})?.value, "Custom variant property") + + // Should not have environment variables + XCTAssertNil(defaultValues.first(where: { $0.key == "custom_global_env_property"})) + XCTAssertNil(defaultValues.first(where: { $0.key == "custom_variant_env_property"})) + + // Should not have non-project custom properties + XCTAssertNil(defaultValues.first(where: { $0.key == "custom_global_fastlane_property"})) + XCTAssertNil(defaultValues.first(where: { $0.key == "custom_variant_fastlane_property"})) + } + // MARK: - iOSVariants.Destination tests func testParsingiOSVariantDestintation() { func makeVariant(destination: String?) throws -> iOSVariant { try iOSVariant(name: "Variant Name", versionName: "1.0.0", versionNumber: 0, appIcon: nil, appName: nil, storeDestination: destination, - custom: nil, idSuffix: "beta", bundleID: nil, variantSigning: nil, globalSigning: validSigning, globalPostSwitchScript: "echo global", variantPostSwitchScript: "echo variant") + idSuffix: "beta", bundleID: nil, globalCustomProperties: nil, variantCustomProperties: nil, + globalSigning: validSigning, debugSigning: nil, releaseSigning: nil, globalPostSwitchScript: nil, variantPostSwitchScript: nil) } // Should not throw if valid destination is provided @@ -438,7 +439,6 @@ class iOSVariantTests: XCTestCase { XCTAssertNoThrow(try makeVariant(destination: "aPpCeNtEr")) // Should read correct value from input - XCTAssertEqual((try? makeVariant(destination: "appcenter"))?.storeDestination, iOSVariant.Destination.appCenter) XCTAssertEqual((try? makeVariant(destination: "appstore"))?.storeDestination, iOSVariant.Destination.appStore) XCTAssertEqual((try? makeVariant(destination: "testflight"))?.storeDestination, iOSVariant.Destination.testFlight) diff --git a/Tests/VariantsTests/InitCommandTests.swift b/Tests/VariantsTests/InitCommandTests.swift index b50a1b60..edf59361 100644 --- a/Tests/VariantsTests/InitCommandTests.swift +++ b/Tests/VariantsTests/InitCommandTests.swift @@ -5,11 +5,11 @@ // Created by Arthur Alves // +// swiftlint:disable line_length + import XCTest import class Foundation.Bundle -// swiftlint:disable line_length - final class InitCommandTests: XCTestCase { func testUsage_help() throws { let arguments = ["init", "--help"] @@ -33,8 +33,9 @@ final class InitCommandTests: XCTestCase { let output = try CLIExecutor.shared.run(with: arguments) XCTAssertEqual(output, expectedOutput) } - - #warning("Test 'testUsage_noExtraArguments' will always fail when running from Xcode.") + + /// Note: + /// Test testUsage_noExtraArguments' will always fail when running from Xcode. func testUsage_noExtraArguments() throws { let arguments = ["init"] diff --git a/Variants.xcodeproj/project.pbxproj b/Variants.xcodeproj/project.pbxproj index d83e7a8b..608fc7f1 100644 --- a/Variants.xcodeproj/project.pbxproj +++ b/Variants.xcodeproj/project.pbxproj @@ -3,7 +3,7 @@ archiveVersion = 1; classes = { }; - objectVersion = 52; + objectVersion = 54; objects = { /* Begin PBXAggregateTarget section */ @@ -32,7 +32,9 @@ /* End PBXAggregateTarget section */ /* Begin PBXBuildFile section */ + 2D72CCA82D478A6000B01883 /* iOSTargetExtensionTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D72CCA72D478A6000B01883 /* iOSTargetExtensionTests.swift */; }; 2D99DF062820856A004A36E1 /* iOSVariantTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D99DF052820856A004A36E1 /* iOSVariantTests.swift */; }; + 2DFF30112D438AEF00F8CF7B /* iOSExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2DFF30102D438AEF00F8CF7B /* iOSExtension.swift */; }; 3907EE7026FDF9CE00311EE6 /* XcodeProjFactoryTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3907EE6F26FDF9CE00311EE6 /* XcodeProjFactoryTests.swift */; }; 3940C5A127074B1A00FEA51D /* LogData.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3940C5A027074B1A00FEA51D /* LogData.swift */; }; 397811EF26F397A900643F91 /* Data+WriteTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 397811EE26F397A900643F91 /* Data+WriteTests.swift */; }; @@ -104,7 +106,7 @@ 8EDC550C25592F5800A9CDFF /* iOSProjectTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8EDC550B25592F5800A9CDFF /* iOSProjectTests.swift */; }; 8EE24235256BA98C00F66F61 /* iOSSigning.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8EE24234256BA98C00F66F61 /* iOSSigning.swift */; }; BEAA71ED255A012900E9D4D9 /* AndroidProjectTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = BEAA71EC255A012900E9D4D9 /* AndroidProjectTests.swift */; }; - BEAA720C255E5C3E00E9D4D9 /* MockXCcodeConfigFactory.swift in Sources */ = {isa = PBXBuildFile; fileRef = BEAA720B255E5C3E00E9D4D9 /* MockXCcodeConfigFactory.swift */; }; + BEAA720C255E5C3E00E9D4D9 /* MockXCodeConfigFactory.swift in Sources */ = {isa = PBXBuildFile; fileRef = BEAA720B255E5C3E00E9D4D9 /* MockXCodeConfigFactory.swift */; }; BEAA7212255E5C4100E9D4D9 /* MockGradleScriptFactory.swift in Sources */ = {isa = PBXBuildFile; fileRef = BEAA71FF255E5BBB00E9D4D9 /* MockGradleScriptFactory.swift */; }; BEAA7219255E5C6C00E9D4D9 /* MockFastlaneFactory.swift in Sources */ = {isa = PBXBuildFile; fileRef = BEAA7218255E5C6C00E9D4D9 /* MockFastlaneFactory.swift */; }; C52A0A5429263483007CE315 /* UserInputTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = C52A0A5329263483007CE315 /* UserInputTests.swift */; }; @@ -172,7 +174,9 @@ /* End PBXCopyFilesBuildPhase section */ /* Begin PBXFileReference section */ + 2D72CCA72D478A6000B01883 /* iOSTargetExtensionTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = iOSTargetExtensionTests.swift; sourceTree = ""; }; 2D99DF052820856A004A36E1 /* iOSVariantTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = iOSVariantTests.swift; sourceTree = ""; }; + 2DFF30102D438AEF00F8CF7B /* iOSExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = iOSExtension.swift; sourceTree = ""; }; 3907EE6F26FDF9CE00311EE6 /* XcodeProjFactoryTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = XcodeProjFactoryTests.swift; sourceTree = ""; }; 3940C5A027074B1A00FEA51D /* LogData.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LogData.swift; sourceTree = ""; }; 397811EE26F397A900643F91 /* Data+WriteTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Data+WriteTests.swift"; sourceTree = ""; }; @@ -214,7 +218,7 @@ 8EE24234256BA98C00F66F61 /* iOSSigning.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = iOSSigning.swift; sourceTree = ""; }; BEAA71EC255A012900E9D4D9 /* AndroidProjectTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AndroidProjectTests.swift; sourceTree = ""; }; BEAA71FF255E5BBB00E9D4D9 /* MockGradleScriptFactory.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockGradleScriptFactory.swift; sourceTree = ""; }; - BEAA720B255E5C3E00E9D4D9 /* MockXCcodeConfigFactory.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockXCcodeConfigFactory.swift; sourceTree = ""; }; + BEAA720B255E5C3E00E9D4D9 /* MockXCodeConfigFactory.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockXCodeConfigFactory.swift; sourceTree = ""; }; BEAA7218255E5C6C00E9D4D9 /* MockFastlaneFactory.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockFastlaneFactory.swift; sourceTree = ""; }; C52A0A5329263483007CE315 /* UserInputTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserInputTests.swift; sourceTree = ""; }; C52A0A5529266B1B007CE315 /* iOSSigningTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = iOSSigningTests.swift; sourceTree = ""; }; @@ -338,6 +342,7 @@ 8EDC54E125554B8C00A9CDFF /* CustomProperty+EnvironmentVarTests.swift */, BEAA71EC255A012900E9D4D9 /* AndroidProjectTests.swift */, 8EDC550B25592F5800A9CDFF /* iOSProjectTests.swift */, + 2D72CCA72D478A6000B01883 /* iOSTargetExtensionTests.swift */, 2D99DF052820856A004A36E1 /* iOSVariantTests.swift */, 8E6ABBAF25C03F05006A62FE /* VariantsFileFactoryTests.swift */, 8E1B9C0E254AB51300DD0204 /* Resources */, @@ -377,7 +382,7 @@ isa = PBXGroup; children = ( BEAA71FF255E5BBB00E9D4D9 /* MockGradleScriptFactory.swift */, - BEAA720B255E5C3E00E9D4D9 /* MockXCcodeConfigFactory.swift */, + BEAA720B255E5C3E00E9D4D9 /* MockXCodeConfigFactory.swift */, BEAA7218255E5C6C00E9D4D9 /* MockFastlaneFactory.swift */, 397811F526F4BCF800643F91 /* MockLogger.swift */, 7C460E7E281C1DDE00BBF15D /* MockVariant.swift */, @@ -539,6 +544,7 @@ 8EE24234256BA98C00F66F61 /* iOSSigning.swift */, OBJ_59 /* iOSTarget.swift */, OBJ_60 /* iOSVariant.swift */, + 2DFF30102D438AEF00F8CF7B /* iOSExtension.swift */, ); path = iOS; sourceTree = ""; @@ -601,6 +607,7 @@ buildPhases = ( 8E1B9CC9254AC1E700DD0204 /* Headers */, 8E1B9CCA254AC1E700DD0204 /* Sources */, + 2D70A7582D0854F000DF5272 /* ShellScript */, 8E1B9CCB254AC1E700DD0204 /* Frameworks */, 8E1B9CCC254AC1E700DD0204 /* Resources */, ); @@ -643,6 +650,7 @@ buildConfigurationList = OBJ_379 /* Build configuration list for PBXNativeTarget "Variants" */; buildPhases = ( OBJ_382 /* Sources */, + 2D70A7562D08548E00DF5272 /* ShellScript */, OBJ_421 /* Frameworks */, 8E1BA14E254C43A900DD0204 /* Embed Frameworks */, ); @@ -750,6 +758,45 @@ }; /* End PBXResourcesBuildPhase section */ +/* Begin PBXShellScriptBuildPhase section */ + 2D70A7562D08548E00DF5272 /* ShellScript */ = { + isa = PBXShellScriptBuildPhase; + alwaysOutOfDate = 1; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + ); + outputFileListPaths = ( + ); + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "SWIFT_PACKAGE_DIR=\"${BUILD_DIR%Build/*}SourcePackages/artifacts\"\nSWIFTLINT_CMD=$(ls \"$SWIFT_PACKAGE_DIR\"/swiftlintplugins/SwiftLintBinary/SwiftLintBinary.artifactbundle/swiftlint-*/bin/swiftlint | head -n 1)\n\nif test -f \"$SWIFTLINT_CMD\" 2>&1\nthen\n \"$SWIFTLINT_CMD\"\nelse\n echo \"warning: `swiftlint` command not found - See https://github.com/realm/SwiftLint#installation for installation instructions.\"\nfi\n"; + }; + 2D70A7582D0854F000DF5272 /* ShellScript */ = { + isa = PBXShellScriptBuildPhase; + alwaysOutOfDate = 1; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + ); + outputFileListPaths = ( + ); + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "SWIFT_PACKAGE_DIR=\"${BUILD_DIR%Build/*}SourcePackages/artifacts\"\nSWIFTLINT_CMD=$(ls \"$SWIFT_PACKAGE_DIR\"/swiftlintplugins/SwiftLintBinary/SwiftLintBinary.artifactbundle/swiftlint-*/bin/swiftlint | head -n 1)\n\nif test -f \"$SWIFTLINT_CMD\" 2>&1\nthen\n \"$SWIFTLINT_CMD\"\nelse\n echo \"warning: `swiftlint` command not found - See https://github.com/realm/SwiftLint#installation for installation instructions.\"\nfi\n"; + }; +/* End PBXShellScriptBuildPhase section */ + /* Begin PBXSourcesBuildPhase section */ 8E1B9CCA254AC1E700DD0204 /* Sources */ = { isa = PBXSourcesBuildPhase; @@ -784,6 +831,7 @@ E3BE3E742820336700A31096 /* Errors.swift in Sources */, 3940C5A127074B1A00FEA51D /* LogData.swift in Sources */, 8E1BA0C3254BFF6400DD0204 /* Switch.swift in Sources */, + 2DFF30112D438AEF00F8CF7B /* iOSExtension.swift in Sources */, 8E1B9D9D254AC26F00DD0204 /* Platform.swift in Sources */, 8E1B9EFC254AC2A900DD0204 /* iOSConfiguration.swift in Sources */, 8E1B9E3B254AC28F00DD0204 /* XCConfigFactory.swift in Sources */, @@ -812,7 +860,7 @@ 7C460E81281C1E8100BBF15D /* MockProject.swift in Sources */, 8E1B9F3C254AC31A00DD0204 /* InitializerTests.swift in Sources */, BEAA7212255E5C4100E9D4D9 /* MockGradleScriptFactory.swift in Sources */, - BEAA720C255E5C3E00E9D4D9 /* MockXCcodeConfigFactory.swift in Sources */, + BEAA720C255E5C3E00E9D4D9 /* MockXCodeConfigFactory.swift in Sources */, E39502A9256DB16B00484DCE /* String+WriteTests.swift in Sources */, 397811FB26F4CD3300643F91 /* VerboseLoggerTests.swift in Sources */, 2D99DF062820856A004A36E1 /* iOSVariantTests.swift in Sources */, @@ -832,6 +880,7 @@ 7C460E7F281C1DDE00BBF15D /* MockVariant.swift in Sources */, 8E8A48CA255307B20056F79F /* GradleScriptFactoryTests.swift in Sources */, 8E8A491025543F920056F79F /* SpecHelperTests.swift in Sources */, + 2D72CCA82D478A6000B01883 /* iOSTargetExtensionTests.swift in Sources */, 7C460E83281C30B900BBF15D /* CommandTests.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; @@ -1485,8 +1534,8 @@ isa = XCRemoteSwiftPackageReference; repositoryURL = "https://github.com/kylef/PathKit.git"; requirement = { - kind = exactVersion; - version = 1.0.1; + kind = upToNextMajorVersion; + minimumVersion = 1.0.1; }; }; 8E1BA0FC254C3F9700DD0204 /* XCRemoteSwiftPackageReference "Yams" */ = { @@ -1501,8 +1550,8 @@ isa = XCRemoteSwiftPackageReference; repositoryURL = "https://github.com/tuist/xcodeproj.git"; requirement = { - kind = exactVersion; - version = 8.3.1; + kind = upToNextMajorVersion; + minimumVersion = 8.3.1; }; }; 8E1BA10E254C3FFF00DD0204 /* XCRemoteSwiftPackageReference "swift-argument-parser" */ = { @@ -1510,7 +1559,7 @@ repositoryURL = "https://github.com/apple/swift-argument-parser.git"; requirement = { kind = upToNextMajorVersion; - minimumVersion = 0.3.1; + minimumVersion = 1.0.0; }; }; 8E8A483325514BE00056F79F /* XCRemoteSwiftPackageReference "Stencil" */ = { diff --git a/docs/CUSTOM_PROPERTY.md b/docs/CUSTOM_PROPERTY.md index 40c65fc2..8c160c2f 100644 --- a/docs/CUSTOM_PROPERTY.md +++ b/docs/CUSTOM_PROPERTY.md @@ -40,7 +40,7 @@ property = VARIANTS_PARAMS[:NAME_OF_PROPERTY] ```yaml ios: xcodeproj: ... - targets: + target: ... variants: default: diff --git a/docs/GITHUB_ACTION.md b/docs/GITHUB_ACTION.md index 1aaf02dc..d308d3b8 100644 --- a/docs/GITHUB_ACTION.md +++ b/docs/GITHUB_ACTION.md @@ -11,7 +11,7 @@ If Github Actions is your CI and you use the [Github-hosted macOS runner](https: ```yaml - uses: backbase/variants@main with: - version: 1.2.1 + version: 1.3.0 spec: variants.yml platform: ios variant: beta diff --git a/docs/STORE_DESTINATION.md b/docs/STORE_DESTINATION.md index e4426ced..2626b6c8 100644 --- a/docs/STORE_DESTINATION.md +++ b/docs/STORE_DESTINATION.md @@ -54,24 +54,24 @@ Assume we have an iOS project, which production variant (default) should deploy ```yaml ios: xcodeproj: ... - targets: + target: ... variants: - default: - version_name: 0.0.1 - version_number: 1 - store_destination: AppStore - ... - beta: - version_name: 0.0.1 - version_number: 1 - store_destination: TestFlight - ... - enterprise_release: - version_name: 0.0.1 - version_number: 1 - store_destination: AppCenter - ... + default: + version_name: 0.0.1 + version_number: 1 + store_destination: AppStore + ... + beta: + version_name: 0.0.1 + version_number: 1 + store_destination: TestFlight + ... + enterprise_release: + version_name: 0.0.1 + version_number: 1 + store_destination: AppCenter + ... ``` #### Example for Android @@ -84,16 +84,16 @@ android: app_name: ... variants: - default: - version_name: 0.0.1 - version_code: 1 - store_destination: PlayStore - ... - enterprise_release: - version_name: 0.0.1 - version_code: 1 - store_destination: AppCenter - ... + default: + version_name: 0.0.1 + version_code: 1 + store_destination: PlayStore + ... + enterprise_release: + version_name: 0.0.1 + version_code: 1 + store_destination: AppCenter + ... ``` Now, all we have to do is to switch to the correct variant and instruct *fastlane* to deploy as usual: diff --git a/docs/USAGE.md b/docs/USAGE.md index e249b16b..7c9c3770 100644 --- a/docs/USAGE.md +++ b/docs/USAGE.md @@ -17,6 +17,9 @@ Commands: * [1. Initialize](#initialize) * [Variants Spec](#variants-spec) + * [Environment variables injection](#enviromental-variables-injection) + * [iOS: Bundle ID](#configuring-bundleid) + * [iOS: Signing App Extensions](#signing-extensions) * [Custom configuration](#custom-configuration) * [Signing configuration (iOS only)](#signing-configuration) * [2. Setup](#setup-multiple-build-variants-with-full-fastlane-integration) @@ -58,7 +61,7 @@ It will generate a variants.yml file in the base folder of your project > NOTE: Edit the file variants.yml accordingly. #### Variants Spec -Your `variants.yml` spec will contain all the necessary fields. The information within `xcodeproj` and `targets` sections are populated automatically if a `.xcodeproj` is found in your working directory - otherwise, you'll be asked to update the placeholders in this file. It comes with one variant named `default`, which will be used whenever a variant isn't specified. You can then include custom variants, for which the following settings are required: +Your `variants.yml` spec will contain all the necessary fields. The information within `xcodeproj` and `target` section are populated automatically if a `.xcodeproj` is found in your working directory - otherwise, you'll be asked to update the placeholders in this file. It comes with one variant named `default`, which will be used whenever a variant isn't specified. You can then include custom variants, for which the following settings are required: * `name` * `version_name` * `version_number` @@ -66,46 +69,45 @@ Your `variants.yml` spec will contain all the necessary fields. The information ```yaml ios: xcodeproj: SampleProject.xcodeproj - targets: - SampleProject: + target: name: SampleApp bundle_id: com.sample.app test_target: SampleProjectTests app_icon: AppIcon source: - path: Sources - info: Sources/Info.plist - # Path to folder that will serve as parent to folder Variants/ - config: Sources + path: Sources + info: Sources/Info.plist + # Path to folder that will serve as parent to folder Variants/ + config: Sources variants: - # Default variant is mandatory, do not remove - default: - version_name: 0.0.1 - version_number: {{ envVars.VERSION_CODE }} - store_destination: AppStore - # This is an optional field to override the default app name per variant - app_name: App Marketing Name - custom: - - name: apiBaseUrl - value: https://sample.com/ - destination: project - postSwitchScript: |- - echo default Variant Done Switching - BETA: - id_suffix: beta - app_icon: AppIcon.beta - version_name: 0.0.1 - version_number: 13 - store_destination: TestFlight - custom: - - name: apiBaseUrl - value: https://sample-beta.com/ - destination: project - - key: OTHER_SWIFT_FLAGS - value: $(inherited) -DBETA - destination: project + # Default variant is mandatory, do not remove + default: + version_name: 0.0.1 + version_number: {{ envVars.VERSION_CODE }} + store_destination: AppStore + # This is an optional field to override the default app name per variant + app_name: App Marketing Name + custom: + - name: apiBaseUrl + value: https://sample.com/ + destination: project + postSwitchScript: |- + echo default Variant Done Switching + BETA: + id_suffix: beta + app_icon: AppIcon.beta + version_name: 0.0.1 + version_number: 13 + store_destination: TestFlight + custom: + - name: apiBaseUrl + value: https://sample-beta.com/ + destination: project + - key: OTHER_SWIFT_FLAGS + value: $(inherited) -DBETA + destination: project postSwitchScript: |- - echo global Done Switching + echo global Done Switching ``` ```yaml android: @@ -192,6 +194,44 @@ For example: Target BundleID is `com.sample.App` and variant `bundle_id` is `com *Note: `id_suffix` and `bundle_id` are not compatible and must not be provided at the same time. Only one of the configurations can be provided per each variant.* +#### Signing extensions + +Variants can also help signing extensions via Match. In order to do so simply include the extensions in the `variants.yml` as the following: + +```yaml +ios: + xcodeproj: SampleProject.xcodeproj + target: + ... + extensions: + - name: TestWidgetExtension + bundle_suffix: TestWidgetExtension + signed: true + - name: AnotherTestWidgetExtension + bundle_id: com.test.MyApp.AnotherTestWidgetExtension + signed: true + variants: + ... +``` + +The `bundle_id` will be generated for each extension marked with `signed: true` and added to the `app_identifier` property in the Matchfile for Match to sign the application. + +There are two ways to configure the Bundle ID generation: + +If a `id_suffix` is provided in the extension config the BundleID will be generated based on the selected variant BundleID and the suffix provided. +For example: Variant BundleID is `com.sample.App.beta` and extension `id_sufix` is `TestWidgetExtension`, the generated BundleID will be `com.sample.App.beta.TestWidgetExtension` + +If a `bundle_id` is provided in the extension config the BundleID will be generated based on the bundle ID provided. +For example: Variant BundleID is `com.sample.App.beta` and extension `bundle_id` is `com.test.MyApp.AnotherTestWidgetExtension`, the generated BundleID will be `com.test.MyApp.AnotherTestWidgetExtension` + +*Note: `id_suffix` and `bundle_id` are not compatible and must not be provided at the same time. Only one of the configurations can be provided per each extension.* + +#### iOS Signing + +For iOS only you can combine signing options for debug and or release depending on the project needs. + +For more information see our [iOS Signing documentation](ios/IOS_SIGNING.md). + #### Custom configuration Configuration through custom properties can bring a lot of value to your variants, such as defining different API base URLs, or credentials using environment variables. This allows us to also define its destination. Certain properties should not be available to the project but to fastlane and vice-versa. diff --git a/docs/ios/IOS_SIGNING.md b/docs/ios/IOS_SIGNING.md new file mode 100644 index 00000000..78b1bfb8 --- /dev/null +++ b/docs/ios/IOS_SIGNING.md @@ -0,0 +1,73 @@ +## iOS Signing + +Variants allows you to highly customize the signing for each variant and each type of signing (debug vs release). Internally, every variant will always have a debug signing and a release signing that are selected using the following priority order: + +`release_signing` (from variant configuration) > `signing` (from variant configuration) > `signing` (from global configuration) + +The same priority applies to the debug signing + +`debug_signing` (from variant configuration) > `signing` (from variant configuration) > `signing` (from global configuration) + +`auto_detect_signing_identity` boolean flag will determine if Variants should attempt to fetch the matching signing certificate from the Keychain Access automatically. If this fails, it will fall back to manual signing gracefully. Auto detect is enabled by default. + +If no signing configuration is found, an error is thrown to the user so the `variants.yml` must be updated. + +### Configuration example + +Given the following `variants.yml`: + +```yaml +ios: + xcodeproj: SampleProject.xcodeproj + target: + ... + extensions: + - name: TestWidgetExtension + bundle_suffix: TestWidgetExtension + signed: true + variants: + default: + ... # does not include a signing, debug_signing, or release_signing + beta: + signing: + match_url: "git@github.com:sample/match.git" + team_name: "Beta Backbase B.V." + team_id: "DEF7654321D" + export_method: "appstore" + auto_detect_signing_identity: true + staging: + signing: + match_url: "git@github.com:sample/match.git" + team_name: "Staging Backbase B.V." + team_id: "ABD1234567D" + export_method: "appstore" + debug_signing: + style: automatic + prod: + release_signing: + match_url: "git@github.com:sample/match.git" + team_name: "Prod Backbase B.V." + team_id: "GHI8765432D" + export_method: "appstore" + debug_signing: + style: automatic + signing: + match_url: "git@github.com:sample/match.git" + team_name: "Backbase B.V." + team_id: "ABC1234567D" + export_method: "appstore" + auto_detect_signing_identity: true +``` + +This is the output in Xcode and Matchfile: + +- For the `default` variant, both the release signing and debug signing will come from the global signing configuration. +- For the `beta` variant, both the release signing and debug signing are overwritten by the local variant configuration. +- For the `staging` variant, both the release signing and debug signing are overwritten, but in this case, the debug signing is overwritten by the `debug_signing` configuration and the release signing is overwritten by the `signing` configuration +- For the `prod` variant, both the release signing and debug signing are overwritten, but in this case, the debug signing is overwritten by the `debug_signing` configuration and the release signing is overwritten by the `release_signing` configuration + +### Target extension signing + +The signing will also affect the sign of the target extensions listed in the `extensions` configuration that are marked with `signed` as `true`. They will follow the same rules as the main app as mentioned above. + +Extensions will inherit the signing configuration from the respective (debug / release) signing configuration of the current selected variant. diff --git a/samples/ios/VariantsTestApp/Gemfile b/samples/ios/VariantsTestApp/Gemfile index b05da1af..d99c679d 100644 --- a/samples/ios/VariantsTestApp/Gemfile +++ b/samples/ios/VariantsTestApp/Gemfile @@ -1,11 +1,9 @@ source 'https://rubygems.org' -gem 'cocoapods', '1.11.3' +gem 'cocoapods' gem 'cocoapods-art' gem 'fastlane' gem 'slather' -gem 'json', '2.3.0' -gem 'nokogiri', '1.13.5' plugins_path = File.join(File.dirname(__FILE__), 'fastlane', 'Pluginfile') eval_gemfile(plugins_path) if File.exist?(plugins_path) diff --git a/samples/ios/VariantsTestApp/Gemfile.lock b/samples/ios/VariantsTestApp/Gemfile.lock index 51c7c9af..13423148 100644 --- a/samples/ios/VariantsTestApp/Gemfile.lock +++ b/samples/ios/VariantsTestApp/Gemfile.lock @@ -1,49 +1,61 @@ GEM remote: https://rubygems.org/ specs: - CFPropertyList (3.0.6) + CFPropertyList (3.0.7) + base64 + nkf rexml - activesupport (6.1.7.2) + activesupport (7.1.5.1) + base64 + benchmark (>= 0.3) + bigdecimal concurrent-ruby (~> 1.0, >= 1.0.2) + connection_pool (>= 2.2.5) + drb i18n (>= 1.6, < 2) + logger (>= 1.4.2) minitest (>= 5.1) + mutex_m + securerandom (>= 0.3) tzinfo (~> 2.0) - zeitwerk (~> 2.3) - addressable (2.8.1) - public_suffix (>= 2.0.2, < 6.0) + addressable (2.8.7) + public_suffix (>= 2.0.2, < 7.0) algoliasearch (1.27.5) httpclient (~> 2.8, >= 2.8.3) json (>= 1.5.1) - artifactory (3.0.15) + artifactory (3.0.17) atomos (0.1.3) - aws-eventstream (1.2.0) - aws-partitions (1.711.0) - aws-sdk-core (3.170.0) - aws-eventstream (~> 1, >= 1.0.2) - aws-partitions (~> 1, >= 1.651.0) - aws-sigv4 (~> 1.5) + aws-eventstream (1.3.0) + aws-partitions (1.1039.0) + aws-sdk-core (3.216.0) + aws-eventstream (~> 1, >= 1.3.0) + aws-partitions (~> 1, >= 1.992.0) + aws-sigv4 (~> 1.9) jmespath (~> 1, >= 1.6.1) - aws-sdk-kms (1.62.0) - aws-sdk-core (~> 3, >= 3.165.0) - aws-sigv4 (~> 1.1) - aws-sdk-s3 (1.119.1) - aws-sdk-core (~> 3, >= 3.165.0) + aws-sdk-kms (1.97.0) + aws-sdk-core (~> 3, >= 3.216.0) + aws-sigv4 (~> 1.5) + aws-sdk-s3 (1.178.0) + aws-sdk-core (~> 3, >= 3.216.0) aws-sdk-kms (~> 1) - aws-sigv4 (~> 1.4) - aws-sigv4 (1.5.2) + aws-sigv4 (~> 1.5) + aws-sigv4 (1.11.0) aws-eventstream (~> 1, >= 1.0.2) babosa (1.0.4) + base64 (0.2.0) + benchmark (0.4.0) + bigdecimal (3.1.9) claide (1.1.0) clamp (1.3.2) - cocoapods (1.11.3) + cocoapods (1.16.2) addressable (~> 2.8) claide (>= 1.0.2, < 2.0) - cocoapods-core (= 1.11.3) + cocoapods-core (= 1.16.2) cocoapods-deintegrate (>= 1.0.3, < 2.0) - cocoapods-downloader (>= 1.4.0, < 2.0) + cocoapods-downloader (>= 2.1, < 3.0) cocoapods-plugins (>= 1.0.0, < 2.0) cocoapods-search (>= 1.0.0, < 2.0) - cocoapods-trunk (>= 1.4.0, < 2.0) + cocoapods-trunk (>= 1.6.0, < 2.0) cocoapods-try (>= 1.1.0, < 2.0) colored2 (~> 3.1) escape (~> 0.0.4) @@ -51,11 +63,11 @@ GEM gh_inspector (~> 1.0) molinillo (~> 0.8.0) nap (~> 1.0) - ruby-macho (>= 1.0, < 3.0) - xcodeproj (>= 1.21.0, < 2.0) - cocoapods-art (1.1.0) - cocoapods-core (1.11.3) - activesupport (>= 5.0, < 7) + ruby-macho (>= 2.3.0, < 3.0) + xcodeproj (>= 1.27.0, < 2.0) + cocoapods-art (1.1.1) + cocoapods-core (1.16.2) + activesupport (>= 5.0, < 8) addressable (~> 2.8) algoliasearch (~> 1.0) concurrent-ruby (~> 1.1) @@ -65,7 +77,7 @@ GEM public_suffix (~> 4.0) typhoeus (~> 1.0) cocoapods-deintegrate (1.0.5) - cocoapods-downloader (1.6.3) + cocoapods-downloader (2.1) cocoapods-plugins (1.0.0) nap cocoapods-search (1.0.1) @@ -78,19 +90,20 @@ GEM colored2 (3.1.2) commander (4.6.0) highline (~> 2.0.0) - concurrent-ruby (1.2.0) + concurrent-ruby (1.3.5) + connection_pool (2.5.0) declarative (0.0.20) - digest-crc (0.6.4) + digest-crc (0.6.5) rake (>= 12.0.0, < 14.0.0) - domain_name (0.5.20190701) - unf (>= 0.0.5, < 1.0.0) + domain_name (0.6.20240107) dotenv (2.8.1) + drb (2.2.1) emoji_regex (3.2.3) escape (0.0.4) ethon (0.16.0) ffi (>= 1.15.0) - excon (0.99.0) - faraday (1.10.3) + excon (0.112.0) + faraday (1.10.4) faraday-em_http (~> 1.0) faraday-em_synchrony (~> 1.0) faraday-excon (~> 1.1) @@ -109,24 +122,24 @@ GEM faraday-em_synchrony (1.0.0) faraday-excon (1.1.0) faraday-httpclient (1.0.1) - faraday-multipart (1.0.4) - multipart-post (~> 2) - faraday-net_http (1.0.1) + faraday-multipart (1.1.0) + multipart-post (~> 2.0) + faraday-net_http (1.0.2) faraday-net_http_persistent (1.2.0) faraday-patron (1.0.0) faraday-rack (1.0.0) faraday-retry (1.0.3) - faraday_middleware (1.2.0) + faraday_middleware (1.2.1) faraday (~> 1.0) - fastimage (2.2.6) - fastlane (2.211.0) + fastimage (2.4.0) + fastlane (2.226.0) CFPropertyList (>= 2.3, < 4.0.0) addressable (>= 2.8, < 3.0.0) artifactory (~> 3.0) aws-sdk-s3 (~> 1.0) babosa (>= 1.0.3, < 2.0.0) bundler (>= 1.12.0, < 3.0.0) - colored + colored (~> 1.2) commander (~> 4.6) dotenv (>= 2.1.1, < 3.0.0) emoji_regex (>= 0.1, < 4.0) @@ -135,43 +148,48 @@ GEM faraday-cookie_jar (~> 0.0.6) faraday_middleware (~> 1.0) fastimage (>= 2.1.0, < 3.0.0) + fastlane-sirp (>= 1.0.0) gh_inspector (>= 1.1.2, < 2.0.0) google-apis-androidpublisher_v3 (~> 0.3) google-apis-playcustomapp_v1 (~> 0.1) + google-cloud-env (>= 1.6.0, < 2.0.0) google-cloud-storage (~> 1.31) highline (~> 2.0) + http-cookie (~> 1.0.5) json (< 3.0.0) jwt (>= 2.1.0, < 3) mini_magick (>= 4.9.4, < 5.0.0) - multipart-post (~> 2.0.0) + multipart-post (>= 2.0.0, < 3.0.0) naturally (~> 2.2) - optparse (~> 0.1.1) + optparse (>= 0.1.1, < 1.0.0) plist (>= 3.1.0, < 4.0.0) rubyzip (>= 2.0.0, < 3.0.0) - security (= 0.1.3) + security (= 0.1.5) simctl (~> 1.6.3) terminal-notifier (>= 2.0.0, < 3.0.0) - terminal-table (>= 1.4.5, < 2.0.0) + terminal-table (~> 3) tty-screen (>= 0.6.3, < 1.0.0) tty-spinner (>= 0.8.0, < 1.0.0) word_wrap (~> 1.0.0) xcodeproj (>= 1.13.0, < 2.0.0) - xcpretty (~> 0.3.0) - xcpretty-travis-formatter (>= 0.0.3) - fastlane-plugin-appcenter (2.0.0) + xcpretty (~> 0.4.0) + xcpretty-travis-formatter (>= 0.0.3, < 2.0.0) + fastlane-plugin-appcenter (2.1.2) fastlane-plugin-lizard (1.3.3) bundler fastlane pry - fastlane-plugin-xcconfig (2.0.0) + fastlane-plugin-xcconfig (2.1.0) fastlane-plugin-xchtmlreport (0.1.1) - ffi (1.15.5) + fastlane-sirp (1.0.0) + sysrandom (~> 1.0) + ffi (1.17.1) fourflusher (2.3.1) fuzzy_match (2.0.4) gh_inspector (1.1.3) - google-apis-androidpublisher_v3 (0.34.0) - google-apis-core (>= 0.9.1, < 2.a) - google-apis-core (0.11.0) + google-apis-androidpublisher_v3 (0.54.0) + google-apis-core (>= 0.11.0, < 2.a) + google-apis-core (0.11.3) addressable (~> 2.5, >= 2.5.1) googleauth (>= 0.16.2, < 2.a) httpclient (>= 2.8.1, < 3.a) @@ -179,80 +197,82 @@ GEM representable (~> 3.0) retriable (>= 2.0, < 4.a) rexml - webrick - google-apis-iamcredentials_v1 (0.16.0) - google-apis-core (>= 0.9.1, < 2.a) - google-apis-playcustomapp_v1 (0.12.0) - google-apis-core (>= 0.9.1, < 2.a) - google-apis-storage_v1 (0.19.0) - google-apis-core (>= 0.9.0, < 2.a) - google-cloud-core (1.6.0) - google-cloud-env (~> 1.0) + google-apis-iamcredentials_v1 (0.17.0) + google-apis-core (>= 0.11.0, < 2.a) + google-apis-playcustomapp_v1 (0.13.0) + google-apis-core (>= 0.11.0, < 2.a) + google-apis-storage_v1 (0.31.0) + google-apis-core (>= 0.11.0, < 2.a) + google-cloud-core (1.7.1) + google-cloud-env (>= 1.0, < 3.a) google-cloud-errors (~> 1.0) google-cloud-env (1.6.0) faraday (>= 0.17.3, < 3.0) - google-cloud-errors (1.3.0) - google-cloud-storage (1.44.0) + google-cloud-errors (1.4.0) + google-cloud-storage (1.47.0) addressable (~> 2.8) digest-crc (~> 0.4) google-apis-iamcredentials_v1 (~> 0.1) - google-apis-storage_v1 (~> 0.19.0) + google-apis-storage_v1 (~> 0.31.0) google-cloud-core (~> 1.6) googleauth (>= 0.16.2, < 2.a) mini_mime (~> 1.0) - googleauth (1.3.0) + googleauth (1.8.1) faraday (>= 0.17.3, < 3.a) jwt (>= 1.4, < 3.0) - memoist (~> 0.16) multi_json (~> 1.11) os (>= 0.9, < 2.0) signet (>= 0.16, < 2.a) highline (2.0.3) - http-cookie (1.0.5) + http-cookie (1.0.8) domain_name (~> 0.5) httpclient (2.8.3) - i18n (1.12.0) + i18n (1.14.6) concurrent-ruby (~> 1.0) jmespath (1.6.2) - json (2.3.0) - jwt (2.7.0) - memoist (0.16.2) - method_source (1.0.0) - mini_magick (4.12.0) - mini_mime (1.1.2) - mini_portile2 (2.8.1) - minitest (5.17.0) + json (2.9.1) + jwt (2.10.1) + base64 + logger (1.6.5) + method_source (1.1.0) + mini_magick (4.13.2) + mini_mime (1.1.5) + mini_portile2 (2.8.8) + minitest (5.25.4) molinillo (0.8.0) multi_json (1.15.0) - multipart-post (2.0.0) - nanaimo (0.3.0) + multipart-post (2.4.1) + mutex_m (0.3.0) + nanaimo (0.4.0) nap (1.1.0) naturally (2.2.1) netrc (0.11.0) - nokogiri (1.13.5) - mini_portile2 (~> 2.8.0) + nkf (0.2.0) + nokogiri (1.15.7) + mini_portile2 (~> 2.8.2) racc (~> 1.4) - optparse (0.1.1) + optparse (0.6.0) os (1.1.4) - plist (3.6.0) - pry (0.14.2) + plist (3.7.2) + pry (0.15.2) coderay (~> 1.1) method_source (~> 1.0) public_suffix (4.0.7) - racc (1.6.2) - rake (13.0.6) + racc (1.8.1) + rake (13.2.1) representable (3.2.0) declarative (< 0.1.0) trailblazer-option (>= 0.1.1, < 0.2.0) uber (< 0.2.0) retriable (3.1.2) - rexml (3.2.5) - rouge (2.0.7) + rexml (3.4.0) + rouge (3.28.0) ruby-macho (2.5.1) ruby2_keywords (0.0.5) - rubyzip (2.3.2) - security (0.1.3) - signet (0.17.0) + rubyzip (2.4.1) + securerandom (0.3.2) + security (0.1.5) + signet (0.19.0) addressable (~> 2.8) faraday (>= 0.17.5, < 3.a) jwt (>= 1.5, < 3.0) @@ -260,57 +280,51 @@ GEM simctl (1.6.10) CFPropertyList naturally - slather (2.7.2) + slather (2.8.5) CFPropertyList (>= 2.2, < 4) activesupport clamp (~> 1.3) - nokogiri (~> 1.12) - xcodeproj (~> 1.21) + nokogiri (>= 1.14.3) + xcodeproj (~> 1.27) + sysrandom (1.0.5) terminal-notifier (2.0.0) - terminal-table (1.8.0) - unicode-display_width (~> 1.1, >= 1.1.1) + terminal-table (3.0.2) + unicode-display_width (>= 1.1.1, < 3) trailblazer-option (0.1.2) tty-cursor (0.7.1) - tty-screen (0.8.1) + tty-screen (0.8.2) tty-spinner (0.9.3) tty-cursor (~> 0.7) - typhoeus (1.4.0) + typhoeus (1.4.1) ethon (>= 0.9.0) tzinfo (2.0.6) concurrent-ruby (~> 1.0) uber (0.1.0) - unf (0.1.4) - unf_ext - unf_ext (0.0.8.2) - unicode-display_width (1.8.0) - webrick (1.8.1) + unicode-display_width (2.6.0) word_wrap (1.0.0) - xcodeproj (1.22.0) + xcodeproj (1.27.0) CFPropertyList (>= 2.3.3, < 4.0) atomos (~> 0.1.3) claide (>= 1.0.2, < 2.0) colored2 (~> 3.1) - nanaimo (~> 0.3.0) - rexml (~> 3.2.4) - xcpretty (0.3.0) - rouge (~> 2.0.7) + nanaimo (~> 0.4.0) + rexml (>= 3.3.6, < 4.0) + xcpretty (0.4.0) + rouge (~> 3.28.0) xcpretty-travis-formatter (1.0.1) xcpretty (~> 0.2, >= 0.0.7) - zeitwerk (2.6.7) PLATFORMS ruby DEPENDENCIES - cocoapods (= 1.11.3) + cocoapods (~> 1.16) cocoapods-art fastlane fastlane-plugin-appcenter fastlane-plugin-lizard fastlane-plugin-xcconfig fastlane-plugin-xchtmlreport - json (= 2.3.0) - nokogiri (= 1.13.5) slather BUNDLED WITH diff --git a/samples/ios/VariantsTestApp/VariantsTestApp.xcodeproj/project.pbxproj b/samples/ios/VariantsTestApp/VariantsTestApp.xcodeproj/project.pbxproj index 16ca4ca0..45f28090 100644 --- a/samples/ios/VariantsTestApp/VariantsTestApp.xcodeproj/project.pbxproj +++ b/samples/ios/VariantsTestApp/VariantsTestApp.xcodeproj/project.pbxproj @@ -7,6 +7,14 @@ objects = { /* Begin PBXBuildFile section */ + 2DFD1E682D3FE3B200349BF3 /* WidgetKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 2DFD1E672D3FE3B200349BF3 /* WidgetKit.framework */; }; + 2DFD1E6A2D3FE3B200349BF3 /* SwiftUI.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 2DFD1E692D3FE3B200349BF3 /* SwiftUI.framework */; }; + 2DFD1E6D2D3FE3B200349BF3 /* VariantsWidgetExtensionBundle.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2DFD1E6C2D3FE3B200349BF3 /* VariantsWidgetExtensionBundle.swift */; }; + 2DFD1E6F2D3FE3B200349BF3 /* VariantsWidgetExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2DFD1E6E2D3FE3B200349BF3 /* VariantsWidgetExtension.swift */; }; + 2DFD1E712D3FE3B400349BF3 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 2DFD1E702D3FE3B400349BF3 /* Assets.xcassets */; }; + 2DFD1E752D3FE3B400349BF3 /* VariantsWidgetExtensionExtension.appex in Embed Foundation Extensions */ = {isa = PBXBuildFile; fileRef = 2DFD1E652D3FE3B200349BF3 /* VariantsWidgetExtensionExtension.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; }; + 2DFD1E812D3FE6ED00349BF3 /* VariantsTestAppTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2DFD1E802D3FE6ED00349BF3 /* VariantsTestAppTests.swift */; }; + 42717DE08F8D9A239BB8BDD3 /* Variants.swift in Sources */ = {isa = PBXBuildFile; fileRef = B512AE731F1EAB7155E4C339 /* Variants.swift */; }; 8E00D0E729967BD4009F995B /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8E00D0E629967BD4009F995B /* AppDelegate.swift */; }; 8E00D0E929967BD4009F995B /* SceneDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8E00D0E829967BD4009F995B /* SceneDelegate.swift */; }; 8E00D0EB29967BD4009F995B /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8E00D0EA29967BD4009F995B /* ViewController.swift */; }; @@ -15,9 +23,49 @@ 8E00D0F329967BD5009F995B /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 8E00D0F129967BD5009F995B /* LaunchScreen.storyboard */; }; /* End PBXBuildFile section */ +/* Begin PBXContainerItemProxy section */ + 2DFD1E732D3FE3B400349BF3 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 8E00D0DB29967BD4009F995B /* Project object */; + proxyType = 1; + remoteGlobalIDString = 2DFD1E642D3FE3B200349BF3; + remoteInfo = VariantsWidgetExtensionExtension; + }; + 2DFD1E822D3FE6ED00349BF3 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 8E00D0DB29967BD4009F995B /* Project object */; + proxyType = 1; + remoteGlobalIDString = 8E00D0E229967BD4009F995B; + remoteInfo = VariantsTestApp; + }; +/* End PBXContainerItemProxy section */ + +/* Begin PBXCopyFilesBuildPhase section */ + 2DFD1E792D3FE3B400349BF3 /* Embed Foundation Extensions */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 2147483647; + dstPath = ""; + dstSubfolderSpec = 13; + files = ( + 2DFD1E752D3FE3B400349BF3 /* VariantsWidgetExtensionExtension.appex in Embed Foundation Extensions */, + ); + name = "Embed Foundation Extensions"; + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXCopyFilesBuildPhase section */ + /* Begin PBXFileReference section */ - 78DBE119A2B49BE740D07F6E /* variants.xcconfig */ = {isa = PBXFileReference; explicitFileType = text.xcconfig; lastKnownFileType = text.xcconfig; name = variants.xcconfig; path = variants.xcconfig; sourceTree = ""; }; - 8E00D0E329967BD4009F995B /* VariantsTestApp.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = VariantsTestApp.app; sourceTree = BUILT_PRODUCTS_DIR; }; + 2DFD1E652D3FE3B200349BF3 /* VariantsWidgetExtensionExtension.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = VariantsWidgetExtensionExtension.appex; sourceTree = BUILT_PRODUCTS_DIR; }; + 2DFD1E672D3FE3B200349BF3 /* WidgetKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = WidgetKit.framework; path = System/Library/Frameworks/WidgetKit.framework; sourceTree = SDKROOT; }; + 2DFD1E692D3FE3B200349BF3 /* SwiftUI.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = SwiftUI.framework; path = System/Library/Frameworks/SwiftUI.framework; sourceTree = SDKROOT; }; + 2DFD1E6C2D3FE3B200349BF3 /* VariantsWidgetExtensionBundle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VariantsWidgetExtensionBundle.swift; sourceTree = ""; }; + 2DFD1E6E2D3FE3B200349BF3 /* VariantsWidgetExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VariantsWidgetExtension.swift; sourceTree = ""; }; + 2DFD1E702D3FE3B400349BF3 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; + 2DFD1E722D3FE3B400349BF3 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + 2DFD1E7E2D3FE6ED00349BF3 /* VariantsTestAppTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = VariantsTestAppTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; + 2DFD1E802D3FE6ED00349BF3 /* VariantsTestAppTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VariantsTestAppTests.swift; sourceTree = ""; }; + 78DBE119A2B49BE740D07F6E /* variants.xcconfig */ = {isa = PBXFileReference; explicitFileType = text.xcconfig; path = variants.xcconfig; sourceTree = ""; }; + 8E00D0E329967BD4009F995B /* .app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; name = .app; path = VariantsTestApp.app; sourceTree = BUILT_PRODUCTS_DIR; }; 8E00D0E629967BD4009F995B /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 8E00D0E829967BD4009F995B /* SceneDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SceneDelegate.swift; sourceTree = ""; }; 8E00D0EA29967BD4009F995B /* ViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewController.swift; sourceTree = ""; }; @@ -25,10 +73,26 @@ 8E00D0EF29967BD5009F995B /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 8E00D0F229967BD5009F995B /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 8E00D0F429967BD5009F995B /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; - B512AE731F1EAB7155E4C339 /* Variants.swift */ = {isa = PBXFileReference; explicitFileType = sourcecode.swift; lastKnownFileType = sourcecode.swift; name = Variants.swift; path = Variants.swift; sourceTree = ""; }; + B512AE731F1EAB7155E4C339 /* Variants.swift */ = {isa = PBXFileReference; explicitFileType = sourcecode.swift; path = Variants.swift; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ + 2DFD1E622D3FE3B200349BF3 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 2DFD1E6A2D3FE3B200349BF3 /* SwiftUI.framework in Frameworks */, + 2DFD1E682D3FE3B200349BF3 /* WidgetKit.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 2DFD1E7B2D3FE6ED00349BF3 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; 8E00D0E029967BD4009F995B /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; @@ -39,44 +103,41 @@ /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ - 230F48C564AB3C5C82B10FD8 /* Variants */ = { - isa = PBXGroup; - children = ( - ); - name = Variants; - path = Variants; - sourceTree = ""; - }; - 3C00DF7AC3E9535AC87DE841 /* Variants */ = { + 2DFD1E662D3FE3B200349BF3 /* Frameworks */ = { isa = PBXGroup; children = ( - 78DBE119A2B49BE740D07F6E /* variants.xcconfig */, - B512AE731F1EAB7155E4C339 /* Variants.swift */, + 2DFD1E672D3FE3B200349BF3 /* WidgetKit.framework */, + 2DFD1E692D3FE3B200349BF3 /* SwiftUI.framework */, ); - name = Variants; - path = Variants; + name = Frameworks; sourceTree = ""; }; - 59718C9FC2D37D0BF4D2A70C /* Variants */ = { + 2DFD1E6B2D3FE3B200349BF3 /* VariantsWidgetExtension */ = { isa = PBXGroup; children = ( + 2DFD1E6C2D3FE3B200349BF3 /* VariantsWidgetExtensionBundle.swift */, + 2DFD1E6E2D3FE3B200349BF3 /* VariantsWidgetExtension.swift */, + 2DFD1E702D3FE3B400349BF3 /* Assets.xcassets */, + 2DFD1E722D3FE3B400349BF3 /* Info.plist */, ); - name = Variants; - path = Variants; + path = VariantsWidgetExtension; sourceTree = ""; }; - 7811E91440F5CC74E43491D4 /* Variants */ = { + 2DFD1E7F2D3FE6ED00349BF3 /* VariantsTestAppTests */ = { isa = PBXGroup; children = ( + 2DFD1E802D3FE6ED00349BF3 /* VariantsTestAppTests.swift */, ); - name = Variants; - path = Variants; + path = VariantsTestAppTests; sourceTree = ""; }; 8E00D0DA29967BD4009F995B = { isa = PBXGroup; children = ( 8E00D0E529967BD4009F995B /* VariantsTestApp */, + 2DFD1E6B2D3FE3B200349BF3 /* VariantsWidgetExtension */, + 2DFD1E7F2D3FE6ED00349BF3 /* VariantsTestAppTests */, + 2DFD1E662D3FE3B200349BF3 /* Frameworks */, 8E00D0E429967BD4009F995B /* Products */, ); sourceTree = ""; @@ -84,7 +145,9 @@ 8E00D0E429967BD4009F995B /* Products */ = { isa = PBXGroup; children = ( - 8E00D0E329967BD4009F995B /* VariantsTestApp.app */, + 8E00D0E329967BD4009F995B /* .app */, + 2DFD1E652D3FE3B200349BF3 /* VariantsWidgetExtensionExtension.appex */, + 2DFD1E7E2D3FE6ED00349BF3 /* VariantsTestAppTests.xctest */, ); name = Products; sourceTree = ""; @@ -99,44 +162,58 @@ 8E00D0EF29967BD5009F995B /* Assets.xcassets */, 8E00D0F129967BD5009F995B /* LaunchScreen.storyboard */, 8E00D0F429967BD5009F995B /* Info.plist */, - 3C00DF7AC3E9535AC87DE841 /* Variants */, EAFEB4948D49564FD23C7C18 /* Variants */, - 230F48C564AB3C5C82B10FD8 /* Variants */, - DF977394A4BABFEBDD9E1123 /* Variants */, - D0217B79AF9976E6B68D08EE /* Variants */, - 7811E91440F5CC74E43491D4 /* Variants */, - 59718C9FC2D37D0BF4D2A70C /* Variants */, ); path = VariantsTestApp; sourceTree = ""; }; - D0217B79AF9976E6B68D08EE /* Variants */ = { - isa = PBXGroup; - children = ( - ); - name = Variants; - path = Variants; - sourceTree = ""; - }; - DF977394A4BABFEBDD9E1123 /* Variants */ = { - isa = PBXGroup; - children = ( - ); - name = Variants; - path = Variants; - sourceTree = ""; - }; EAFEB4948D49564FD23C7C18 /* Variants */ = { isa = PBXGroup; children = ( + 78DBE119A2B49BE740D07F6E /* variants.xcconfig */, + B512AE731F1EAB7155E4C339 /* Variants.swift */, ); - name = Variants; path = Variants; sourceTree = ""; }; /* End PBXGroup section */ /* Begin PBXNativeTarget section */ + 2DFD1E642D3FE3B200349BF3 /* VariantsWidgetExtensionExtension */ = { + isa = PBXNativeTarget; + buildConfigurationList = 2DFD1E762D3FE3B400349BF3 /* Build configuration list for PBXNativeTarget "VariantsWidgetExtensionExtension" */; + buildPhases = ( + 2DFD1E612D3FE3B200349BF3 /* Sources */, + 2DFD1E622D3FE3B200349BF3 /* Frameworks */, + 2DFD1E632D3FE3B200349BF3 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = VariantsWidgetExtensionExtension; + productName = VariantsWidgetExtensionExtension; + productReference = 2DFD1E652D3FE3B200349BF3 /* VariantsWidgetExtensionExtension.appex */; + productType = "com.apple.product-type.app-extension"; + }; + 2DFD1E7D2D3FE6ED00349BF3 /* VariantsTestAppTests */ = { + isa = PBXNativeTarget; + buildConfigurationList = 2DFD1E842D3FE6ED00349BF3 /* Build configuration list for PBXNativeTarget "VariantsTestAppTests" */; + buildPhases = ( + 2DFD1E7A2D3FE6ED00349BF3 /* Sources */, + 2DFD1E7B2D3FE6ED00349BF3 /* Frameworks */, + 2DFD1E7C2D3FE6ED00349BF3 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + 2DFD1E832D3FE6ED00349BF3 /* PBXTargetDependency */, + ); + name = VariantsTestAppTests; + productName = VariantsTestAppTests; + productReference = 2DFD1E7E2D3FE6ED00349BF3 /* VariantsTestAppTests.xctest */; + productType = "com.apple.product-type.bundle.unit-test"; + }; 8E00D0E229967BD4009F995B /* VariantsTestApp */ = { isa = PBXNativeTarget; buildConfigurationList = 8E00D0F729967BD5009F995B /* Build configuration list for PBXNativeTarget "VariantsTestApp" */; @@ -144,14 +221,16 @@ 8E00D0DF29967BD4009F995B /* Sources */, 8E00D0E029967BD4009F995B /* Frameworks */, 8E00D0E129967BD4009F995B /* Resources */, + 2DFD1E792D3FE3B400349BF3 /* Embed Foundation Extensions */, ); buildRules = ( ); dependencies = ( + 2DFD1E742D3FE3B400349BF3 /* PBXTargetDependency */, ); name = VariantsTestApp; productName = VariantsTestApp; - productReference = 8E00D0E329967BD4009F995B /* VariantsTestApp.app */; + productReference = 8E00D0E329967BD4009F995B /* .app */; productType = "com.apple.product-type.application"; }; /* End PBXNativeTarget section */ @@ -161,9 +240,16 @@ isa = PBXProject; attributes = { BuildIndependentTargetsInParallel = 1; - LastSwiftUpdateCheck = 1410; + LastSwiftUpdateCheck = 1540; LastUpgradeCheck = 1410; TargetAttributes = { + 2DFD1E642D3FE3B200349BF3 = { + CreatedOnToolsVersion = 15.4; + }; + 2DFD1E7D2D3FE6ED00349BF3 = { + CreatedOnToolsVersion = 15.4; + TestTargetID = 8E00D0E229967BD4009F995B; + }; 8E00D0E229967BD4009F995B = { CreatedOnToolsVersion = 14.1; }; @@ -183,11 +269,28 @@ projectRoot = ""; targets = ( 8E00D0E229967BD4009F995B /* VariantsTestApp */, + 2DFD1E642D3FE3B200349BF3 /* VariantsWidgetExtensionExtension */, + 2DFD1E7D2D3FE6ED00349BF3 /* VariantsTestAppTests */, ); }; /* End PBXProject section */ /* Begin PBXResourcesBuildPhase section */ + 2DFD1E632D3FE3B200349BF3 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 2DFD1E712D3FE3B400349BF3 /* Assets.xcassets in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 2DFD1E7C2D3FE6ED00349BF3 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; 8E00D0E129967BD4009F995B /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; @@ -201,6 +304,23 @@ /* End PBXResourcesBuildPhase section */ /* Begin PBXSourcesBuildPhase section */ + 2DFD1E612D3FE3B200349BF3 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 2DFD1E6D2D3FE3B200349BF3 /* VariantsWidgetExtensionBundle.swift in Sources */, + 2DFD1E6F2D3FE3B200349BF3 /* VariantsWidgetExtension.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 2DFD1E7A2D3FE6ED00349BF3 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 2DFD1E812D3FE6ED00349BF3 /* VariantsTestAppTests.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; 8E00D0DF29967BD4009F995B /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; @@ -208,12 +328,25 @@ 8E00D0EB29967BD4009F995B /* ViewController.swift in Sources */, 8E00D0E729967BD4009F995B /* AppDelegate.swift in Sources */, 8E00D0E929967BD4009F995B /* SceneDelegate.swift in Sources */, - "TEMP_BB6F0DB5-B674-40C8-99E9-2E703E5B30CC" /* (null) in Sources */, + 42717DE08F8D9A239BB8BDD3 /* Variants.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXSourcesBuildPhase section */ +/* Begin PBXTargetDependency section */ + 2DFD1E742D3FE3B400349BF3 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 2DFD1E642D3FE3B200349BF3 /* VariantsWidgetExtensionExtension */; + targetProxy = 2DFD1E732D3FE3B400349BF3 /* PBXContainerItemProxy */; + }; + 2DFD1E832D3FE6ED00349BF3 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 8E00D0E229967BD4009F995B /* VariantsTestApp */; + targetProxy = 2DFD1E822D3FE6ED00349BF3 /* PBXContainerItemProxy */; + }; +/* End PBXTargetDependency section */ + /* Begin PBXVariantGroup section */ 8E00D0EC29967BD4009F995B /* Main.storyboard */ = { isa = PBXVariantGroup; @@ -234,6 +367,120 @@ /* End PBXVariantGroup section */ /* Begin XCBuildConfiguration section */ + 2DFD1E772D3FE3B400349BF3 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; + ASSETCATALOG_COMPILER_WIDGET_BACKGROUND_COLOR_NAME = WidgetBackground; + CODE_SIGN_IDENTITY = "Apple Distribution: Backbase B.V. (ABC1234567D)"; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + DEVELOPMENT_TEAM = ABC1234567D; + ENABLE_USER_SCRIPT_SANDBOXING = YES; + GCC_C_LANGUAGE_STANDARD = gnu17; + GENERATE_INFOPLIST_FILE = YES; + INFOPLIST_FILE = VariantsWidgetExtension/Info.plist; + INFOPLIST_KEY_CFBundleDisplayName = VariantsWidgetExtension; + INFOPLIST_KEY_NSHumanReadableCopyright = ""; + IPHONEOS_DEPLOYMENT_TARGET = 17.5; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@executable_path/../../Frameworks", + ); + LOCALIZATION_PREFERS_STRING_CATALOGS = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.backbase.VariantsTestApp.VariantsWidgetExtension; + PRODUCT_NAME = "$(TARGET_NAME)"; + PROVISIONING_PROFILE_SPECIFIER = ""; + SKIP_INSTALL = YES; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = "DEBUG $(inherited)"; + SWIFT_EMIT_LOC_STRINGS = YES; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Debug; + }; + 2DFD1E782D3FE3B400349BF3 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; + ASSETCATALOG_COMPILER_WIDGET_BACKGROUND_COLOR_NAME = WidgetBackground; + CODE_SIGN_IDENTITY = "Apple Distribution: Backbase B.V. (ABC1234567D)"; + CODE_SIGN_STYLE = Manual; + CURRENT_PROJECT_VERSION = 1; + DEVELOPMENT_TEAM = ABC1234567D; + ENABLE_USER_SCRIPT_SANDBOXING = YES; + GCC_C_LANGUAGE_STANDARD = gnu17; + GENERATE_INFOPLIST_FILE = YES; + INFOPLIST_FILE = VariantsWidgetExtension/Info.plist; + INFOPLIST_KEY_CFBundleDisplayName = VariantsWidgetExtension; + INFOPLIST_KEY_NSHumanReadableCopyright = ""; + IPHONEOS_DEPLOYMENT_TARGET = 17.5; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@executable_path/../../Frameworks", + ); + LOCALIZATION_PREFERS_STRING_CATALOGS = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.backbase.VariantsTestApp.VariantsWidgetExtension; + PRODUCT_NAME = "$(TARGET_NAME)"; + PROVISIONING_PROFILE_SPECIFIER = "match AppStore com.backbase.VariantsTestApp.VariantsWidgetExtension"; + SKIP_INSTALL = YES; + SWIFT_EMIT_LOC_STRINGS = YES; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Release; + }; + 2DFD1E852D3FE6ED00349BF3 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + BUNDLE_LOADER = "$(TEST_HOST)"; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + ENABLE_USER_SCRIPT_SANDBOXING = YES; + GCC_C_LANGUAGE_STANDARD = gnu17; + GENERATE_INFOPLIST_FILE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 17.5; + LOCALIZATION_PREFERS_STRING_CATALOGS = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.backbase.VariantsTestApp.beta.VariantsTestAppTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = "DEBUG $(inherited)"; + SWIFT_EMIT_LOC_STRINGS = NO; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/VariantsTestApp BETA.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/VariantsTestApp BETA"; + }; + name = Debug; + }; + 2DFD1E862D3FE6ED00349BF3 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + BUNDLE_LOADER = "$(TEST_HOST)"; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + ENABLE_USER_SCRIPT_SANDBOXING = YES; + GCC_C_LANGUAGE_STANDARD = gnu17; + GENERATE_INFOPLIST_FILE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 17.5; + LOCALIZATION_PREFERS_STRING_CATALOGS = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.backbase.VariantsTestApp.beta.VariantsTestAppTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_EMIT_LOC_STRINGS = NO; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/VariantsTestApp BETA.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/VariantsTestApp BETA"; + }; + name = Release; + }; 8E00D0F529967BD5009F995B /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { @@ -352,11 +599,13 @@ isa = XCBuildConfiguration; baseConfigurationReference = 78DBE119A2B49BE740D07F6E /* variants.xcconfig */; buildSettings = { + ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; ASSETCATALOG_COMPILER_APPICON_NAME = "$(V_APP_ICON)"; ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; + CODE_SIGN_IDENTITY = "Apple Distribution: Backbase B.V. (ABC1234567D)"; CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 1; - DEVELOPMENT_TEAM = R22WT7DX79; + DEVELOPMENT_TEAM = ABC1234567D; GENERATE_INFOPLIST_FILE = YES; INFOPLIST_FILE = VariantsTestApp/Info.plist; INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; @@ -371,7 +620,7 @@ MARKETING_VERSION = 1.0; PRODUCT_BUNDLE_IDENTIFIER = "$(V_BUNDLE_ID)"; PRODUCT_NAME = "$(V_APP_NAME)"; - PROVISIONING_PROFILE_SPECIFIER = "$(V_MATCH_PROFILE)"; + PROVISIONING_PROFILE_SPECIFIER = ""; SWIFT_EMIT_LOC_STRINGS = YES; SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2"; @@ -382,11 +631,13 @@ isa = XCBuildConfiguration; baseConfigurationReference = 78DBE119A2B49BE740D07F6E /* variants.xcconfig */; buildSettings = { + ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; ASSETCATALOG_COMPILER_APPICON_NAME = "$(V_APP_ICON)"; ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; - CODE_SIGN_STYLE = Automatic; + CODE_SIGN_IDENTITY = "Apple Distribution: Backbase B.V. (ABC1234567D)"; + CODE_SIGN_STYLE = Manual; CURRENT_PROJECT_VERSION = 1; - DEVELOPMENT_TEAM = R22WT7DX79; + DEVELOPMENT_TEAM = ABC1234567D; GENERATE_INFOPLIST_FILE = YES; INFOPLIST_FILE = VariantsTestApp/Info.plist; INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; @@ -411,6 +662,24 @@ /* End XCBuildConfiguration section */ /* Begin XCConfigurationList section */ + 2DFD1E762D3FE3B400349BF3 /* Build configuration list for PBXNativeTarget "VariantsWidgetExtensionExtension" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 2DFD1E772D3FE3B400349BF3 /* Debug */, + 2DFD1E782D3FE3B400349BF3 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 2DFD1E842D3FE6ED00349BF3 /* Build configuration list for PBXNativeTarget "VariantsTestAppTests" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 2DFD1E852D3FE6ED00349BF3 /* Debug */, + 2DFD1E862D3FE6ED00349BF3 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; 8E00D0DE29967BD4009F995B /* Build configuration list for PBXProject "VariantsTestApp" */ = { isa = XCConfigurationList; buildConfigurations = ( diff --git a/samples/ios/VariantsTestApp/VariantsTestApp.xcodeproj/xcshareddata/xcschemes/VariantsTestApp.xcscheme b/samples/ios/VariantsTestApp/VariantsTestApp.xcodeproj/xcshareddata/xcschemes/VariantsTestApp.xcscheme new file mode 100644 index 00000000..4b5e74fc --- /dev/null +++ b/samples/ios/VariantsTestApp/VariantsTestApp.xcodeproj/xcshareddata/xcschemes/VariantsTestApp.xcscheme @@ -0,0 +1,78 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/samples/ios/VariantsTestApp/VariantsTestApp.xcodeproj/xcshareddata/xcschemes/VariantsWidgetExtensionExtension.xcscheme b/samples/ios/VariantsTestApp/VariantsTestApp.xcodeproj/xcshareddata/xcschemes/VariantsWidgetExtensionExtension.xcscheme new file mode 100644 index 00000000..689002de --- /dev/null +++ b/samples/ios/VariantsTestApp/VariantsTestApp.xcodeproj/xcshareddata/xcschemes/VariantsWidgetExtensionExtension.xcscheme @@ -0,0 +1,112 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/samples/ios/VariantsTestApp/VariantsTestApp/Info.plist b/samples/ios/VariantsTestApp/VariantsTestApp/Info.plist index e9f6b0fc..f2addf35 100644 --- a/samples/ios/VariantsTestApp/VariantsTestApp/Info.plist +++ b/samples/ios/VariantsTestApp/VariantsTestApp/Info.plist @@ -16,6 +16,8 @@ $(V_VERSION_NUMBER) OTHER_SWIFT_FLAGS $(OTHER_SWIFT_FLAGS) + SAMPLE_FASTLANE_PROPERTY + $(SAMPLE_FASTLANE_PROPERTY) UIApplicationSceneManifest UIApplicationSupportsMultipleScenes @@ -35,5 +37,7 @@ + custom_global_property + $(custom_global_property) diff --git a/samples/ios/VariantsTestApp/VariantsTestApp/Variants/Variants.swift b/samples/ios/VariantsTestApp/VariantsTestApp/Variants/Variants.swift index 8abb9ab9..cf4e9319 100644 --- a/samples/ios/VariantsTestApp/VariantsTestApp/Variants/Variants.swift +++ b/samples/ios/VariantsTestApp/VariantsTestApp/Variants/Variants.swift @@ -6,6 +6,7 @@ // import Foundation + public struct Variants { static let configuration: [String: Any] = { guard let infoDictionary = Bundle.main.infoDictionary else { @@ -16,10 +17,12 @@ public struct Variants { // MARK: - ConfigurationValueKey /// Custom configuration values coming from variants.yml as enum cases - public enum ConfigurationValueKey: String { - + + public enum ConfigurationValueKey: String { case OTHER_SWIFT_FLAGS + case custom_global_property } + static func configurationValue(for key: ConfigurationValueKey) -> Any? { return Self.configuration[key.rawValue] } diff --git a/samples/ios/VariantsTestApp/VariantsTestApp/Variants/variants.xcconfig b/samples/ios/VariantsTestApp/VariantsTestApp/Variants/variants.xcconfig index c0b27b34..b1a5f988 100644 --- a/samples/ios/VariantsTestApp/VariantsTestApp/Variants/variants.xcconfig +++ b/samples/ios/VariantsTestApp/VariantsTestApp/Variants/variants.xcconfig @@ -1,7 +1,8 @@ -V_APP_ICON = AppIcon -V_VERSION_NAME = 0.0.1 -V_MATCH_PROFILE = match AppStore com.backbase.VariantsTestApp OTHER_SWIFT_FLAGS = $(inherited) -V_BUNDLE_ID = com.backbase.VariantsTestApp +V_APP_ICON = AppIcon V_APP_NAME = VariantsTestApp +V_BUNDLE_ID = com.backbase.VariantsTestApp +V_MATCH_PROFILE = match AppStore com.backbase.VariantsTestApp +V_VERSION_NAME = 0.0.1 V_VERSION_NUMBER = 1 +custom_global_property = Overridden value diff --git a/samples/ios/VariantsTestApp/VariantsTestApp/ViewController.swift b/samples/ios/VariantsTestApp/VariantsTestApp/ViewController.swift index e836985f..4ba95de4 100644 --- a/samples/ios/VariantsTestApp/VariantsTestApp/ViewController.swift +++ b/samples/ios/VariantsTestApp/VariantsTestApp/ViewController.swift @@ -7,9 +7,4 @@ import UIKit -class ViewController: UIViewController { - override func viewDidLoad() { - super.viewDidLoad() - // Do any additional setup after loading the view. - } -} +class ViewController: UIViewController { } diff --git a/samples/ios/VariantsTestApp/VariantsTestAppTests/VariantsTestAppTests.swift b/samples/ios/VariantsTestApp/VariantsTestAppTests/VariantsTestAppTests.swift new file mode 100644 index 00000000..0aba91ec --- /dev/null +++ b/samples/ios/VariantsTestApp/VariantsTestAppTests/VariantsTestAppTests.swift @@ -0,0 +1,35 @@ +// +// VariantsTestAppTests.swift +// VariantsTestAppTests +// +// Created by Gabriel Rodrigues Minucci on 21/01/2025. +// + +import XCTest + +final class VariantsTestAppTests: XCTestCase { + + override func setUpWithError() throws { + // Put setup code here. This method is called before the invocation of each test method in the class. + } + + override func tearDownWithError() throws { + // Put teardown code here. This method is called after the invocation of each test method in the class. + } + + func testExample() throws { + // This is an example of a functional test case. + // Use XCTAssert and related functions to verify your tests produce the correct results. + // Any test you write for XCTest can be annotated as throws and async. + // Mark your test throws to produce an unexpected failure when your test encounters an uncaught error. + // Mark your test async to allow awaiting for asynchronous code to complete. Check the results with assertions afterwards. + } + + func testPerformanceExample() throws { + // This is an example of a performance test case. + measure { + // Put the code you want to measure the time of here. + } + } + +} diff --git a/samples/ios/VariantsTestApp/VariantsWidgetExtension/Assets.xcassets/AccentColor.colorset/Contents.json b/samples/ios/VariantsTestApp/VariantsWidgetExtension/Assets.xcassets/AccentColor.colorset/Contents.json new file mode 100644 index 00000000..eb878970 --- /dev/null +++ b/samples/ios/VariantsTestApp/VariantsWidgetExtension/Assets.xcassets/AccentColor.colorset/Contents.json @@ -0,0 +1,11 @@ +{ + "colors" : [ + { + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/samples/ios/VariantsTestApp/VariantsWidgetExtension/Assets.xcassets/AppIcon.appiconset/Contents.json b/samples/ios/VariantsTestApp/VariantsWidgetExtension/Assets.xcassets/AppIcon.appiconset/Contents.json new file mode 100644 index 00000000..13613e3e --- /dev/null +++ b/samples/ios/VariantsTestApp/VariantsWidgetExtension/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -0,0 +1,13 @@ +{ + "images" : [ + { + "idiom" : "universal", + "platform" : "ios", + "size" : "1024x1024" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/samples/ios/VariantsTestApp/VariantsWidgetExtension/Assets.xcassets/Contents.json b/samples/ios/VariantsTestApp/VariantsWidgetExtension/Assets.xcassets/Contents.json new file mode 100644 index 00000000..73c00596 --- /dev/null +++ b/samples/ios/VariantsTestApp/VariantsWidgetExtension/Assets.xcassets/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/samples/ios/VariantsTestApp/VariantsWidgetExtension/Assets.xcassets/WidgetBackground.colorset/Contents.json b/samples/ios/VariantsTestApp/VariantsWidgetExtension/Assets.xcassets/WidgetBackground.colorset/Contents.json new file mode 100644 index 00000000..eb878970 --- /dev/null +++ b/samples/ios/VariantsTestApp/VariantsWidgetExtension/Assets.xcassets/WidgetBackground.colorset/Contents.json @@ -0,0 +1,11 @@ +{ + "colors" : [ + { + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/samples/ios/VariantsTestApp/VariantsWidgetExtension/Info.plist b/samples/ios/VariantsTestApp/VariantsWidgetExtension/Info.plist new file mode 100644 index 00000000..0f118fb7 --- /dev/null +++ b/samples/ios/VariantsTestApp/VariantsWidgetExtension/Info.plist @@ -0,0 +1,11 @@ + + + + + NSExtension + + NSExtensionPointIdentifier + com.apple.widgetkit-extension + + + diff --git a/samples/ios/VariantsTestApp/VariantsWidgetExtension/VariantsWidgetExtension.swift b/samples/ios/VariantsTestApp/VariantsWidgetExtension/VariantsWidgetExtension.swift new file mode 100644 index 00000000..4c99a94f --- /dev/null +++ b/samples/ios/VariantsTestApp/VariantsWidgetExtension/VariantsWidgetExtension.swift @@ -0,0 +1,80 @@ +// +// VariantsWidgetExtension.swift +// VariantsWidgetExtension +// +// Created by Gabriel Rodrigues Minucci on 21/01/2025. +// + +import WidgetKit +import SwiftUI + +struct Provider: TimelineProvider { + func placeholder(in context: Context) -> SimpleEntry { + SimpleEntry(date: Date(), emoji: "😀") + } + + func getSnapshot(in context: Context, completion: @escaping (SimpleEntry) -> Void) { + let entry = SimpleEntry(date: Date(), emoji: "😀") + completion(entry) + } + + func getTimeline(in context: Context, completion: @escaping (Timeline) -> Void) { + var entries: [SimpleEntry] = [] + + // Generate a timeline consisting of five entries an hour apart, starting from the current date. + let currentDate = Date() + for hourOffset in 0 ..< 5 { + let entryDate = Calendar.current.date(byAdding: .hour, value: hourOffset, to: currentDate)! + let entry = SimpleEntry(date: entryDate, emoji: "😀") + entries.append(entry) + } + + let timeline = Timeline(entries: entries, policy: .atEnd) + completion(timeline) + } +} + +struct SimpleEntry: TimelineEntry { + let date: Date + let emoji: String +} + +struct VariantsWidgetExtensionEntryView: View { + var entry: Provider.Entry + + var body: some View { + VStack { + Text("Time:") + Text(entry.date, style: .time) + + Text("Emoji:") + Text(entry.emoji) + } + } +} + +struct VariantsWidgetExtension: Widget { + let kind: String = "VariantsWidgetExtension" + + var body: some WidgetConfiguration { + StaticConfiguration(kind: kind, provider: Provider()) { entry in + if #available(iOS 17.0, *) { + VariantsWidgetExtensionEntryView(entry: entry) + .containerBackground(.fill.tertiary, for: .widget) + } else { + VariantsWidgetExtensionEntryView(entry: entry) + .padding() + .background() + } + } + .configurationDisplayName("My Widget") + .description("This is an example widget.") + } +} + +#Preview(as: .systemSmall) { + VariantsWidgetExtension() +} timeline: { + SimpleEntry(date: .now, emoji: "😀") + SimpleEntry(date: .now, emoji: "🤩") +} diff --git a/samples/ios/VariantsTestApp/VariantsWidgetExtension/VariantsWidgetExtensionBundle.swift b/samples/ios/VariantsTestApp/VariantsWidgetExtension/VariantsWidgetExtensionBundle.swift new file mode 100644 index 00000000..9e107420 --- /dev/null +++ b/samples/ios/VariantsTestApp/VariantsWidgetExtension/VariantsWidgetExtensionBundle.swift @@ -0,0 +1,16 @@ +// +// VariantsWidgetExtensionBundle.swift +// VariantsWidgetExtension +// +// Created by Gabriel Rodrigues Minucci on 21/01/2025. +// + +import WidgetKit +import SwiftUI + +@main +struct VariantsWidgetExtensionBundle: WidgetBundle { + var body: some Widget { + VariantsWidgetExtension() + } +} diff --git a/samples/ios/VariantsTestApp/fastlane/Matchfile b/samples/ios/VariantsTestApp/fastlane/Matchfile index c8d2a6ec..e77a5f20 100644 --- a/samples/ios/VariantsTestApp/fastlane/Matchfile +++ b/samples/ios/VariantsTestApp/fastlane/Matchfile @@ -1,5 +1,7 @@ git_url("git@github.com:sample/match.git") storage_mode("git") -# appstore, development, adhoc, enterprise type("appstore") -app_identifier("com.backbase.VariantsTestApp") \ No newline at end of file +app_identifier([ + "com.backbase.VariantsTestApp", + "com.backbase.VariantsTestApp.VariantsWidgetExtension" +]) \ No newline at end of file diff --git a/samples/ios/VariantsTestApp/fastlane/parameters/match_params.rb b/samples/ios/VariantsTestApp/fastlane/parameters/match_params.rb index 53345d49..c007bf87 100644 --- a/samples/ios/VariantsTestApp/fastlane/parameters/match_params.rb +++ b/samples/ios/VariantsTestApp/fastlane/parameters/match_params.rb @@ -11,7 +11,7 @@ # Signing properties coming from Variants YAML spec. Do not change manually TEAMNAME: "Backbase B.V.", - TEAMID: "R22WT7DX79", + TEAMID: "ABC1234567D", EXPORTMETHOD: "appstore", MATCHURL: "git@github.com:sample/match.git", }.freeze \ No newline at end of file diff --git a/samples/ios/VariantsTestApp/variants.yml b/samples/ios/VariantsTestApp/variants.yml index dd2a8319..107f2601 100644 --- a/samples/ios/VariantsTestApp/variants.yml +++ b/samples/ios/VariantsTestApp/variants.yml @@ -1,87 +1,94 @@ # # Auto generated by Variants -# TODO: Replace placeholders with real values if applicable # ios: xcodeproj: VariantsTestApp.xcodeproj - targets: - VariantsTestApp: + target: name: VariantsTestApp bundle_id: com.backbase.VariantsTestApp - test_target: VariantsTestAppTest + test_target: VariantsTestAppTests app_icon: AppIcon source: - path: VariantsTestApp - info: VariantsTestApp/Info.plist - config: VariantsTestApp + path: VariantsTestApp + info: VariantsTestApp/Info.plist + config: VariantsTestApp + extensions: + - name: VariantsWidgetExtension + bundle_suffix: VariantsWidgetExtension + signed: true + - name: OtherExtension + bundle_id: com.variantsTest.OtherExtension + signed: false variants: # Default variant is mandatory, do not remove - default: - version_name: 0.0.1 - version_number: 1 - # 'store_destination' can be: AppStore, TestFlight or AppCenter - store_destination: AppStore - - # - # custom: - Not required. - # - # You can have as many custom fields as possible. - # Only strings allowed. - # - # The value of will be written to 1 of 2 possible destinations: - # - project => variants.xcconfig - # - fastlane => fastlane/parameters/variants_params.rb - # - custom: - - name: OTHER_SWIFT_FLAGS - value: $(inherited) - env: false - destination: project - - name: SAMPLE_FASTLANE_PROPERTY - value: This will be available to fastlane - env: false - destination: fastlane - # - # Sample variant, "beta". - # Only `version_name` and `version_number` are mandatory fields - # - BETA: - id_suffix: beta - # If app_icon isn't specified, the value fallbacks to target.app_icon - app_icon: AppIconYellow - version_name: 0.0.1 - version_number: 1 - # 'store_destination' can be: AppStore, TestFlight or AppCenter - store_destination: AppCenter - - custom: - - name: OTHER_SWIFT_FLAGS - value: $(inherited) - env: false - destination: project - - name: SAMPLE_FASTLANE_PROPERTY - value: This will be available to fastlane on Beta variant - env: false - destination: fastlane + default: + version_name: 0.0.1 + version_number: 1 + # 'store_destination' can be: AppStore, TestFlight or AppCenter + store_destination: AppStore + + # + # custom: - Not required. + # + # You can have as many custom fields as possible. + # Only strings allowed. + # + # The value of will be written to 1 of 2 possible destinations: + # - project => variants.xcconfig + # - fastlane => fastlane/parameters/variants_params.rb + # + custom: + - name: OTHER_SWIFT_FLAGS + value: $(inherited) + env: false + destination: project + - name: SAMPLE_FASTLANE_PROPERTY + value: This will be available to fastlane + env: false + destination: fastlane + - name: custom_global_property + value: Overridden value + destination: project + # + # Sample variant, "beta". + # Only `version_name` and `version_number` are mandatory fields + # + debug_signing: + style: automatic + # 'match_url' isn't mandatory, only if you use Match to sign your app + # match_url: "git@github.com:sample/match.git" + # team_name: "Backbase B.V." + # team_id: "DEF7654321H" + # export_method: "appstore" + BETA: + id_suffix: beta + # If app_icon isn't specified, the value fallbacks to target.app_icon + app_icon: AppIconYellow + version_name: 0.0.1 + version_number: 1 + # 'store_destination' can be: AppStore, TestFlight or AppCenter + store_destination: AppCenter + + custom: + - name: OTHER_SWIFT_FLAGS + value: $(inherited) + env: false + destination: project + - name: SAMPLE_FASTLANE_PROPERTY + value: This will be available to fastlane on Beta variant + env: false + destination: fastlane signing: # 'match_url' isn't mandatory, only if you use Match to sign your app match_url: "git@github.com:sample/match.git" team_name: "Backbase B.V." - team_id: "R22WT7DX79" + team_id: "ABC1234567D" export_method: "appstore" - - # ---------------------------------------------------------------------- - # custom: - Not required. - # - # Same as variant's `custom`, but this will be processed regardless of - # the chosen variant. - # - # Comment or delete section below if necessary. - # ---------------------------------------------------------------------- + auto_detect_signing_identity: true - #custom: - # - name: SAMPLE_PROPERTY - # value: Sample value - # destination: project + custom: + - name: custom_global_property + value: my value + destination: project diff --git a/utils/gyb b/utils/gyb index dece788e..830ec03f 100755 --- a/utils/gyb +++ b/utils/gyb @@ -1,3 +1,3 @@ -#!/usr/bin/env python2.7 +#!/usr/bin/env python3 import gyb -gyb.main() +gyb.main() \ No newline at end of file diff --git a/utils/gyb.py b/utils/gyb.py index a1ff11ee..7e7cb1f7 100755 --- a/utils/gyb.py +++ b/utils/gyb.py @@ -1,9 +1,7 @@ -#!/usr/bin/env python +#!/usr/bin/env python3 # GYB: Generate Your Boilerplate (improved names welcome; at least # this one's short). See -h output for instructions -from __future__ import print_function - import io import os import re @@ -11,18 +9,7 @@ import textwrap import tokenize from bisect import bisect - - -try: - from StringIO import StringIO -except ImportError: - from io import StringIO - - -try: - basestring -except NameError: - basestring = str +from io import StringIO def get_line_starts(s): @@ -63,7 +50,7 @@ def split_lines(s): # Note: Where "# Absorb" appears below, the regexp attempts to eat up # through the end of ${...} and %{...}% constructs. In reality we -# handle this with the Python tokenizer, which avoids mis-detections +# handle this with the Python tokenizer, which avoids misdetections # due to nesting, comments and strings. This extra absorption in the # regexp facilitates testing the regexp on its own, by preventing the # interior of some of these constructs from being treated as literal @@ -732,7 +719,7 @@ def execute(self, context): # If we got a result, the code was an expression, so append # its value if result is not None \ - or (isinstance(result, basestring) and result != ''): + or (isinstance(result, str) and result != ''): from numbers import Number, Integral result_string = None if isinstance(result, Number) and not isinstance(result, Integral): @@ -740,7 +727,7 @@ def execute(self, context): elif isinstance(result, Integral) or isinstance(result, list): result_string = str(result) else: - result_string = StringIO(result).read() + result_string = result context.append_text( result_string, self.filename, self.start_line_number) @@ -1255,17 +1242,21 @@ def succ(a): ast = parse_template(args.file, f.read()) if args.dump: print(ast) + # Allow the template to open files and import .py files relative to its own # directory + saved_cwd = os.getcwd() os.chdir(os.path.dirname(os.path.abspath(args.file))) sys.path = ['.'] + sys.path + result_text = execute_template(ast, args.line_directive, **bindings) if args.target == '-': - sys.stdout.write(execute_template(ast, args.line_directive, **bindings)) + sys.stdout.write(result_text) else: + os.chdir(saved_cwd) with io.open(args.target, 'w', encoding='utf-8', newline='\n') as f: - f.write(execute_template(ast, args.line_directive, **bindings)) + f.write(result_text) if __name__ == '__main__': - main() + main() \ No newline at end of file