fastlane 2.217.0 → 2.219.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (169) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +102 -102
  3. data/deliver/lib/deliver/app_screenshot.rb +2 -2
  4. data/deliver/lib/deliver/app_screenshot_iterator.rb +2 -2
  5. data/deliver/lib/deliver/detect_values.rb +1 -1
  6. data/deliver/lib/deliver/languages.rb +1 -1
  7. data/deliver/lib/deliver/loader.rb +2 -2
  8. data/deliver/lib/deliver/options.rb +4 -4
  9. data/deliver/lib/deliver/runner.rb +1 -1
  10. data/deliver/lib/deliver/sync_screenshots.rb +2 -2
  11. data/deliver/lib/deliver/upload_metadata.rb +4 -4
  12. data/deliver/lib/deliver/upload_price_tier.rb +1 -1
  13. data/deliver/lib/deliver/upload_screenshots.rb +3 -3
  14. data/fastlane/lib/fastlane/action.rb +1 -1
  15. data/fastlane/lib/fastlane/actions/appledoc.rb +1 -1
  16. data/fastlane/lib/fastlane/actions/apteligent.rb +1 -1
  17. data/fastlane/lib/fastlane/actions/backup_xcarchive.rb +1 -1
  18. data/fastlane/lib/fastlane/actions/commit_github_file.rb +2 -2
  19. data/fastlane/lib/fastlane/actions/copy_artifacts.rb +1 -1
  20. data/fastlane/lib/fastlane/actions/create_app_online.rb +1 -1
  21. data/fastlane/lib/fastlane/actions/create_pull_request.rb +1 -1
  22. data/fastlane/lib/fastlane/actions/get_certificates.rb +1 -1
  23. data/fastlane/lib/fastlane/actions/get_github_release.rb +1 -1
  24. data/fastlane/lib/fastlane/actions/get_provisioning_profile.rb +1 -1
  25. data/fastlane/lib/fastlane/actions/github_api.rb +1 -1
  26. data/fastlane/lib/fastlane/actions/gradle.rb +1 -1
  27. data/fastlane/lib/fastlane/actions/install_on_device.rb +1 -1
  28. data/fastlane/lib/fastlane/actions/ipa.rb +1 -1
  29. data/fastlane/lib/fastlane/actions/jazzy.rb +1 -1
  30. data/fastlane/lib/fastlane/actions/oclint.rb +3 -3
  31. data/fastlane/lib/fastlane/actions/opt_out_crash_reporting.rb +2 -2
  32. data/fastlane/lib/fastlane/actions/restore_file.rb +1 -1
  33. data/fastlane/lib/fastlane/actions/set_github_release.rb +1 -1
  34. data/fastlane/lib/fastlane/actions/slather.rb +1 -1
  35. data/fastlane/lib/fastlane/actions/sonar.rb +12 -3
  36. data/fastlane/lib/fastlane/actions/splunkmint.rb +1 -1
  37. data/fastlane/lib/fastlane/actions/spm.rb +76 -2
  38. data/fastlane/lib/fastlane/actions/update_info_plist.rb +1 -1
  39. data/fastlane/lib/fastlane/actions/update_urban_airship_configuration.rb +1 -1
  40. data/fastlane/lib/fastlane/actions/upload_symbols_to_crashlytics.rb +1 -0
  41. data/fastlane/lib/fastlane/actions/upload_symbols_to_sentry.rb +1 -1
  42. data/fastlane/lib/fastlane/actions/upload_to_testflight.rb +2 -2
  43. data/fastlane/lib/fastlane/actions/verify_build.rb +7 -4
  44. data/fastlane/lib/fastlane/actions/xcov.rb +1 -1
  45. data/fastlane/lib/fastlane/cli_tools_distributor.rb +1 -1
  46. data/fastlane/lib/fastlane/command_line_handler.rb +2 -4
  47. data/fastlane/lib/fastlane/commands_generator.rb +2 -2
  48. data/fastlane/lib/fastlane/fast_file.rb +1 -1
  49. data/fastlane/lib/fastlane/helper/dotenv_helper.rb +1 -1
  50. data/fastlane/lib/fastlane/junit_generator.rb +1 -1
  51. data/fastlane/lib/fastlane/lane_manager.rb +1 -2
  52. data/fastlane/lib/fastlane/plugins/template/%gem_name%.gemspec.erb +0 -11
  53. data/fastlane/lib/fastlane/plugins/template/.rubocop.yml +5 -1
  54. data/fastlane/lib/fastlane/plugins/template/Gemfile.erb +27 -0
  55. data/fastlane/lib/fastlane/runner.rb +1 -1
  56. data/fastlane/lib/fastlane/setup/setup.rb +1 -1
  57. data/fastlane/lib/fastlane/swift_lane_manager.rb +2 -5
  58. data/fastlane/lib/fastlane/swift_runner_upgrader.rb +7 -4
  59. data/fastlane/lib/fastlane/version.rb +1 -1
  60. data/fastlane/swift/Actions.swift +1 -1
  61. data/fastlane/swift/Appfile.swift +1 -1
  62. data/fastlane/swift/ArgumentProcessor.swift +1 -1
  63. data/fastlane/swift/Atomic.swift +1 -1
  64. data/fastlane/swift/ControlCommand.swift +1 -1
  65. data/fastlane/swift/Deliverfile.swift +2 -2
  66. data/fastlane/swift/DeliverfileProtocol.swift +4 -4
  67. data/fastlane/swift/Fastlane.swift +79 -27
  68. data/fastlane/swift/Gymfile.swift +2 -2
  69. data/fastlane/swift/GymfileProtocol.swift +2 -2
  70. data/fastlane/swift/LaneFileProtocol.swift +5 -5
  71. data/fastlane/swift/MainProcess.swift +1 -1
  72. data/fastlane/swift/Matchfile.swift +2 -2
  73. data/fastlane/swift/MatchfileProtocol.swift +2 -2
  74. data/fastlane/swift/OptionalConfigValue.swift +1 -1
  75. data/fastlane/swift/Plugins.swift +1 -1
  76. data/fastlane/swift/Precheckfile.swift +2 -2
  77. data/fastlane/swift/PrecheckfileProtocol.swift +2 -2
  78. data/fastlane/swift/RubyCommand.swift +1 -1
  79. data/fastlane/swift/RubyCommandable.swift +1 -1
  80. data/fastlane/swift/Runner.swift +1 -1
  81. data/fastlane/swift/RunnerArgument.swift +1 -1
  82. data/fastlane/swift/Scanfile.swift +2 -2
  83. data/fastlane/swift/ScanfileProtocol.swift +2 -2
  84. data/fastlane/swift/Screengrabfile.swift +2 -2
  85. data/fastlane/swift/ScreengrabfileProtocol.swift +2 -2
  86. data/fastlane/swift/Snapshotfile.swift +2 -2
  87. data/fastlane/swift/SnapshotfileProtocol.swift +2 -2
  88. data/fastlane/swift/SocketClient.swift +1 -1
  89. data/fastlane/swift/SocketClientDelegateProtocol.swift +1 -1
  90. data/fastlane/swift/SocketResponse.swift +1 -1
  91. data/fastlane/swift/formatting/Brewfile.lock.json +26 -18
  92. data/fastlane/swift/main.swift +1 -1
  93. data/fastlane_core/lib/fastlane_core/build_watcher.rb +1 -1
  94. data/fastlane_core/lib/fastlane_core/cert_checker.rb +2 -2
  95. data/fastlane_core/lib/fastlane_core/configuration/configuration_file.rb +1 -1
  96. data/fastlane_core/lib/fastlane_core/device_manager.rb +17 -15
  97. data/fastlane_core/lib/fastlane_core/fastlane_pty.rb +34 -12
  98. data/fastlane_core/lib/fastlane_core/helper.rb +1 -1
  99. data/fastlane_core/lib/fastlane_core/itunes_transporter.rb +5 -2
  100. data/fastlane_core/lib/fastlane_core/project.rb +3 -2
  101. data/fastlane_core/lib/fastlane_core/queue_worker.rb +1 -1
  102. data/fastlane_core/lib/fastlane_core/string_filters.rb +6 -6
  103. data/fastlane_core/lib/fastlane_core/ui/fastlane_runner.rb +2 -2
  104. data/frameit/lib/frameit/editor.rb +4 -4
  105. data/frameit/lib/frameit/trim_box.rb +1 -1
  106. data/gym/lib/gym/error_handler.rb +1 -1
  107. data/gym/lib/gym/generators/package_command_generator_xcode7.rb +3 -3
  108. data/gym/lib/gym/runner.rb +1 -1
  109. data/gym/lib/gym/xcodebuild_fixes/README.md +1 -1
  110. data/match/lib/match/generator.rb +9 -1
  111. data/match/lib/match/module.rb +2 -1
  112. data/match/lib/match/portal_cache.rb +106 -0
  113. data/match/lib/match/portal_fetcher.rb +72 -0
  114. data/match/lib/match/profile_includes.rb +120 -0
  115. data/match/lib/match/runner.rb +79 -175
  116. data/match/lib/match/spaceship_ensure.rb +15 -11
  117. data/match/lib/match/storage/git_storage.rb +8 -3
  118. data/match/lib/match/storage/gitlab/client.rb +1 -1
  119. data/match/lib/match/storage/gitlab_secure_files.rb +1 -1
  120. data/match/lib/match/storage/interface.rb +1 -1
  121. data/match/lib/match/storage/s3_storage.rb +1 -1
  122. data/match/lib/match.rb +3 -0
  123. data/produce/lib/produce/itunes_connect.rb +1 -1
  124. data/scan/lib/scan/detect_values.rb +78 -20
  125. data/scan/lib/scan/options.rb +1 -1
  126. data/scan/lib/scan/runner.rb +1 -1
  127. data/screengrab/lib/screengrab/runner.rb +1 -1
  128. data/sigh/lib/assets/resign.sh +10 -10
  129. data/sigh/lib/sigh/commands_generator.rb +1 -1
  130. data/sigh/lib/sigh/module.rb +98 -0
  131. data/sigh/lib/sigh/options.rb +55 -1
  132. data/sigh/lib/sigh/resign.rb +1 -1
  133. data/sigh/lib/sigh/runner.rb +36 -112
  134. data/snapshot/lib/snapshot/setup.rb +2 -2
  135. data/snapshot/lib/snapshot/simulator_launchers/simulator_launcher.rb +23 -22
  136. data/snapshot/lib/snapshot/simulator_launchers/simulator_launcher_base.rb +2 -2
  137. data/spaceship/lib/spaceship/client.rb +1 -1
  138. data/spaceship/lib/spaceship/connect_api/api_client.rb +1 -1
  139. data/spaceship/lib/spaceship/connect_api/client.rb +4 -4
  140. data/spaceship/lib/spaceship/connect_api/models/app_preview_set.rb +2 -0
  141. data/spaceship/lib/spaceship/connect_api/models/app_screenshot.rb +2 -2
  142. data/spaceship/lib/spaceship/connect_api/models/bundle_id.rb +2 -2
  143. data/spaceship/lib/spaceship/connect_api/models/certificate.rb +2 -2
  144. data/spaceship/lib/spaceship/connect_api/models/device.rb +82 -3
  145. data/spaceship/lib/spaceship/connect_api/models/profile.rb +3 -2
  146. data/spaceship/lib/spaceship/connect_api/tunes/tunes.rb +3 -3
  147. data/spaceship/lib/spaceship/connect_api.rb +2 -0
  148. data/spaceship/lib/spaceship/portal/app.rb +1 -1
  149. data/spaceship/lib/spaceship/portal/app_group.rb +1 -1
  150. data/spaceship/lib/spaceship/test_flight/client.rb +1 -1
  151. data/spaceship/lib/spaceship/test_flight/tester.rb +1 -1
  152. data/spaceship/lib/spaceship/tunes/app_details.rb +2 -2
  153. data/spaceship/lib/spaceship/tunes/app_image.rb +1 -1
  154. data/spaceship/lib/spaceship/tunes/app_review_attachment.rb +1 -1
  155. data/spaceship/lib/spaceship/tunes/app_submission.rb +1 -1
  156. data/spaceship/lib/spaceship/tunes/app_version.rb +5 -5
  157. data/spaceship/lib/spaceship/tunes/build_details.rb +1 -1
  158. data/spaceship/lib/spaceship/tunes/iap.rb +3 -3
  159. data/spaceship/lib/spaceship/tunes/iap_detail.rb +2 -2
  160. data/spaceship/lib/spaceship/tunes/iap_families.rb +1 -1
  161. data/spaceship/lib/spaceship/tunes/iap_family_details.rb +2 -2
  162. data/spaceship/lib/spaceship/tunes/iap_family_list.rb +1 -1
  163. data/spaceship/lib/spaceship/tunes/tunes_client.rb +2 -2
  164. data/supply/lib/supply/client.rb +1 -1
  165. data/supply/lib/supply/setup.rb +1 -1
  166. data/trainer/lib/trainer/junit_generator.rb +1 -1
  167. data/trainer/lib/trainer/test_parser.rb +1 -1
  168. metadata +45 -288
  169. data/fastlane/lib/fastlane/plugins/template/Gemfile +0 -6
@@ -23,7 +23,8 @@ module Match
23
23
  end
24
24
 
25
25
  def self.cert_type_sym(type)
26
- type = type.to_s
26
+ # To determine certificate types to fetch from the portal, we use `Sigh.certificate_types_for_profile_and_platform`, and it returns typed `Spaceship::ConnectAPI::Certificate::CertificateType` with the same values but uppercased, so we downcase them here
27
+ type = type.to_s.downcase
27
28
  return :mac_installer_distribution if type == "mac_installer_distribution"
28
29
  return :developer_id_installer if type == "developer_id_installer"
29
30
  return :developer_id_application if type == "developer_id"
@@ -0,0 +1,106 @@
1
+ require 'fastlane_core/provisioning_profile'
2
+ require 'spaceship/client'
3
+ require_relative 'portal_fetcher'
4
+ module Match
5
+ class Portal
6
+ class Cache
7
+ def self.build(params:, bundle_id_identifiers:)
8
+ require_relative 'profile_includes'
9
+ require 'sigh'
10
+
11
+ profile_type = Sigh.profile_type_for_distribution_type(
12
+ platform: params[:platform],
13
+ distribution_type: params[:type]
14
+ )
15
+
16
+ cache = Portal::Cache.new(
17
+ platform: params[:platform],
18
+ profile_type: profile_type,
19
+ additional_cert_types: params[:additional_cert_types],
20
+ bundle_id_identifiers: bundle_id_identifiers,
21
+ needs_profiles_devices: ProfileIncludes.can_force_include_all_devices?(params: params, notify: true),
22
+ needs_profiles_certificate_content: !ProfileIncludes.can_force_include_all_certificates?(params: params, notify: true),
23
+ include_mac_in_profiles: params[:include_mac_in_profiles]
24
+ )
25
+
26
+ return cache
27
+ end
28
+
29
+ attr_reader :platform, :profile_type, :bundle_id_identifiers, :additional_cert_types, :needs_profiles_devices, :needs_profiles_certificate_content, :include_mac_in_profiles
30
+
31
+ def initialize(platform:, profile_type:, additional_cert_types:, bundle_id_identifiers:, needs_profiles_devices:, needs_profiles_certificate_content:, include_mac_in_profiles:)
32
+ @platform = platform
33
+ @profile_type = profile_type
34
+
35
+ # Bundle Ids
36
+ @bundle_id_identifiers = bundle_id_identifiers
37
+
38
+ # Certs
39
+ @additional_cert_types = additional_cert_types
40
+
41
+ # Profiles
42
+ @needs_profiles_devices = needs_profiles_devices
43
+ @needs_profiles_certificate_content = needs_profiles_certificate_content
44
+
45
+ # Devices
46
+ @include_mac_in_profiles = include_mac_in_profiles
47
+ end
48
+
49
+ def portal_profile(stored_profile_path:, keychain_path:)
50
+ parsed = FastlaneCore::ProvisioningProfile.parse(stored_profile_path, keychain_path)
51
+ uuid = parsed["UUID"]
52
+
53
+ portal_profile = self.profiles.detect { |i| i.uuid == uuid }
54
+
55
+ portal_profile
56
+ end
57
+
58
+ def reset_certificates
59
+ @certificates = nil
60
+ end
61
+
62
+ def forget_portal_profile(portal_profile)
63
+ return unless @profiles && portal_profile
64
+
65
+ @profiles -= [portal_profile]
66
+ end
67
+
68
+ def bundle_ids
69
+ @bundle_ids ||= Match::Portal::Fetcher.bundle_ids(
70
+ bundle_id_identifiers: @bundle_id_identifiers
71
+ )
72
+
73
+ return @bundle_ids.dup
74
+ end
75
+
76
+ def certificates
77
+ @certificates ||= Match::Portal::Fetcher.certificates(
78
+ platform: @platform,
79
+ profile_type: @profile_type,
80
+ additional_cert_types: @additional_cert_types
81
+ )
82
+
83
+ return @certificates.dup
84
+ end
85
+
86
+ def profiles
87
+ @profiles ||= Match::Portal::Fetcher.profiles(
88
+ profile_type: @profile_type,
89
+ needs_profiles_devices: @needs_profiles_devices,
90
+ needs_profiles_certificate_content: @needs_profiles_certificate_content
91
+ )
92
+
93
+ return @profiles.dup
94
+ end
95
+
96
+ def devices
97
+ @devices ||= Match::Portal::Fetcher.devices(
98
+ platform: @platform,
99
+ include_mac_in_profiles: @include_mac_in_profiles
100
+ )
101
+
102
+ return @devices.dup
103
+ end
104
+ end
105
+ end
106
+ end
@@ -0,0 +1,72 @@
1
+ require 'fastlane_core/provisioning_profile'
2
+ require 'spaceship/client'
3
+ require 'spaceship/connect_api/models/profile'
4
+
5
+ module Match
6
+ class Portal
7
+ module Fetcher
8
+ def self.profiles(profile_type:, needs_profiles_devices: false, needs_profiles_certificate_content: false, name: nil)
9
+ includes = ['bundleId']
10
+
11
+ if needs_profiles_devices
12
+ includes += ['devices', 'certificates']
13
+ end
14
+
15
+ if needs_profiles_certificate_content
16
+ includes += ['certificates']
17
+ end
18
+
19
+ profiles = Spaceship::ConnectAPI::Profile.all(
20
+ filter: { profileType: profile_type, name: name }.compact,
21
+ includes: includes.uniq.join(',')
22
+ )
23
+
24
+ profiles
25
+ end
26
+
27
+ def self.certificates(platform:, profile_type:, additional_cert_types:)
28
+ require 'sigh'
29
+ certificate_types = Sigh.certificate_types_for_profile_and_platform(platform: platform, profile_type: profile_type)
30
+
31
+ additional_cert_types ||= []
32
+ additional_cert_types.map! do |cert_type|
33
+ case Match.cert_type_sym(cert_type)
34
+ when :mac_installer_distribution
35
+ Spaceship::ConnectAPI::Certificate::CertificateType::MAC_INSTALLER_DISTRIBUTION
36
+ when :developer_id_installer
37
+ Spaceship::ConnectAPI::Certificate::CertificateType::DEVELOPER_ID_INSTALLER
38
+ end
39
+ end
40
+
41
+ certificate_types += additional_cert_types
42
+
43
+ filter = { certificateType: certificate_types.uniq.sort.join(',') } unless certificate_types.empty?
44
+
45
+ certificates = Spaceship::ConnectAPI::Certificate.all(
46
+ filter: filter
47
+ ).select(&:valid?)
48
+
49
+ certificates
50
+ end
51
+
52
+ def self.devices(platform: nil, include_mac_in_profiles: false)
53
+ devices = Spaceship::ConnectAPI::Device.devices_for_platform(
54
+ platform: platform,
55
+ include_mac_in_profiles: include_mac_in_profiles
56
+ )
57
+
58
+ devices
59
+ end
60
+
61
+ def self.bundle_ids(bundle_id_identifiers: nil)
62
+ filter = { identifier: bundle_id_identifiers.join(',') } if bundle_id_identifiers
63
+
64
+ bundle_ids = Spaceship::ConnectAPI::BundleId.all(
65
+ filter: filter
66
+ )
67
+
68
+ bundle_ids
69
+ end
70
+ end
71
+ end
72
+ end
@@ -0,0 +1,120 @@
1
+ require_relative 'portal_fetcher'
2
+ require_relative 'module'
3
+
4
+ module Match
5
+ class ProfileIncludes
6
+ PROV_TYPES_WITH_DEVICES = [:adhoc, :development]
7
+ PROV_TYPES_WITH_MULTIPLE_CERTIFICATES = [:development]
8
+
9
+ def self.can_force_include?(params:, notify:)
10
+ self.can_force_include_all_devices?(params: params, notify: notify) &&
11
+ self.can_force_include_all_certificates?(params: params, notify: notify)
12
+ end
13
+
14
+ ###############
15
+ #
16
+ # DEVICES
17
+ #
18
+ ###############
19
+
20
+ def self.should_force_include_all_devices?(params:, portal_profile:, cached_devices:)
21
+ return false unless self.can_force_include_all_devices?(params: params)
22
+
23
+ force = devices_differ?(portal_profile: portal_profile, platform: params[:platform], include_mac_in_profiles: params[:include_mac_in_profiles], cached_devices: cached_devices)
24
+
25
+ return force
26
+ end
27
+
28
+ def self.can_force_include_all_devices?(params:, notify: false)
29
+ return false if params[:readonly] || params[:force]
30
+ return false unless params[:force_for_new_devices]
31
+
32
+ provisioning_type = params[:type].to_sym
33
+
34
+ can_force = PROV_TYPES_WITH_DEVICES.include?(provisioning_type)
35
+
36
+ if !can_force && notify
37
+ # App Store provisioning profiles don't contain device identifiers and
38
+ # thus shouldn't be renewed if the device count has changed.
39
+ UI.important("Warning: `force_for_new_devices` is set but is ignored for #{provisioning_type}.")
40
+ UI.important("You can safely stop specifying `force_for_new_devices` when running Match for type '#{provisioning_type}'.")
41
+ end
42
+
43
+ can_force
44
+ end
45
+
46
+ def self.devices_differ?(portal_profile:, platform:, include_mac_in_profiles:, cached_devices:)
47
+ return false unless portal_profile
48
+
49
+ profile_devices = portal_profile.devices || []
50
+
51
+ portal_devices = cached_devices
52
+ portal_devices ||= Match::Portal::Fetcher.devices(platform: platform, include_mac_in_profiles: include_mac_in_profiles)
53
+
54
+ profile_device_ids = profile_devices.map(&:id).sort
55
+ portal_devices_ids = portal_devices.map(&:id).sort
56
+
57
+ devices_differs = profile_device_ids != portal_devices_ids
58
+
59
+ UI.important("Devices in the profile and available on the portal differ. Recreating a profile") if devices_differs
60
+
61
+ return devices_differs
62
+ end
63
+
64
+ ###############
65
+ #
66
+ # CERTIFICATES
67
+ #
68
+ ###############
69
+
70
+ def self.should_force_include_all_certificates?(params:, portal_profile:, cached_certificates:)
71
+ return false unless self.can_force_include_all_certificates?(params: params)
72
+
73
+ force = certificates_differ?(portal_profile: portal_profile, platform: params[:platform], cached_certificates: cached_certificates)
74
+
75
+ return force
76
+ end
77
+
78
+ def self.can_force_include_all_certificates?(params:, notify: false)
79
+ return false if params[:readonly] || params[:force]
80
+ return false unless params[:force_for_new_certificates]
81
+
82
+ unless params[:include_all_certificates]
83
+ UI.important("You specified 'force_for_new_certificates: true', but new certificates will not be added, cause 'include_all_certificates' is 'false'") if notify
84
+ return false
85
+ end
86
+
87
+ provisioning_type = params[:type].to_sym
88
+
89
+ can_force = PROV_TYPES_WITH_MULTIPLE_CERTIFICATES.include?(provisioning_type)
90
+
91
+ if !can_force && notify
92
+ # All other (not development) provisioning profiles don't contain
93
+ # multiple certificates, thus shouldn't be renewed
94
+ # if the certificates count has changed.
95
+ UI.important("Warning: `force_for_new_certificates` is set but is ignored for non-'development' provisioning profiles.")
96
+ UI.important("You can safely stop specifying `force_for_new_certificates` when running Match for '#{provisioning_type}' provisioning profiles.")
97
+ end
98
+
99
+ can_force
100
+ end
101
+
102
+ def self.certificates_differ?(portal_profile:, platform:, cached_certificates:)
103
+ return false unless portal_profile
104
+
105
+ profile_certs = portal_profile.certificates || []
106
+
107
+ portal_certs = cached_certificates
108
+ portal_certs ||= Match::Portal::Fetcher.certificates(platform: platform, profile_type: portal_profile.profile_type)
109
+
110
+ profile_certs_ids = profile_certs.map(&:id).sort
111
+ portal_certs_ids = portal_certs.map(&:id).sort
112
+
113
+ certificates_differ = profile_certs_ids != portal_certs_ids
114
+
115
+ UI.important("Certificates in the profile and available on the portal differ. Recreating a profile") if certificates_differ
116
+
117
+ return certificates_differ
118
+ end
119
+ end
120
+ end
@@ -2,6 +2,8 @@ require 'fastlane_core/cert_checker'
2
2
  require 'fastlane_core/provisioning_profile'
3
3
  require 'fastlane_core/print_table'
4
4
  require 'spaceship/client'
5
+ require 'sigh/module'
6
+
5
7
  require_relative 'generator'
6
8
  require_relative 'module'
7
9
  require_relative 'table_printer'
@@ -10,6 +12,8 @@ require_relative 'utils'
10
12
 
11
13
  require_relative 'storage'
12
14
  require_relative 'encryption'
15
+ require_relative 'profile_includes'
16
+ require_relative 'portal_cache'
13
17
 
14
18
  module Match
15
19
  # rubocop:disable Metrics/ClassLength
@@ -19,6 +23,8 @@ module Match
19
23
 
20
24
  attr_accessor :storage
21
25
 
26
+ attr_accessor :cache
27
+
22
28
  # rubocop:disable Metrics/PerceivedComplexity
23
29
  def run(params)
24
30
  self.files_to_commit = []
@@ -60,12 +66,18 @@ module Match
60
66
 
61
67
  # sometimes we get an array with arrays, this is a bug. To unblock people using match, I suggest we flatten
62
68
  # then in the future address the root cause of https://github.com/fastlane/fastlane/issues/11324
63
- app_identifiers = app_identifiers.flatten
69
+ app_identifiers = app_identifiers.flatten.uniq
64
70
 
65
- # Verify the App ID (as we don't want 'match' to fail at a later point)
66
71
  if spaceship
72
+ # Cache bundle ids, certificates, profiles, and devices.
73
+ self.cache = Portal::Cache.build(
74
+ params: params,
75
+ bundle_id_identifiers: app_identifiers
76
+ )
77
+
78
+ # Verify the App ID (as we don't want 'match' to fail at a later point)
67
79
  app_identifiers.each do |app_identifier|
68
- spaceship.bundle_identifier_exists(username: params[:username], app_identifier: app_identifier, platform: params[:platform])
80
+ spaceship.bundle_identifier_exists(username: params[:username], app_identifier: app_identifier, cached_bundle_ids: self.cache.bundle_ids)
69
81
  end
70
82
  end
71
83
 
@@ -78,17 +90,24 @@ module Match
78
90
  fetch_certificate(params: params, working_directory: storage.working_directory, specific_cert_type: additional_cert_type)
79
91
  end
80
92
 
93
+ profile_type = Sigh.profile_type_for_distribution_type(
94
+ platform: params[:platform],
95
+ distribution_type: params[:type]
96
+ )
97
+
81
98
  cert_ids << cert_id
82
- spaceship.certificates_exists(username: params[:username], certificate_ids: cert_ids) if spaceship
99
+ spaceship.certificates_exists(username: params[:username], certificate_ids: cert_ids, platform: params[:platform], profile_type: profile_type, cached_certificates: self.cache.certificates) if spaceship
83
100
 
84
101
  # Provisioning Profiles
102
+
85
103
  unless params[:skip_provisioning_profiles]
86
104
  app_identifiers.each do |app_identifier|
87
105
  loop do
88
106
  break if fetch_provisioning_profile(params: params,
107
+ profile_type: profile_type,
89
108
  certificate_id: cert_id,
90
109
  app_identifier: app_identifier,
91
- working_directory: storage.working_directory)
110
+ working_directory: storage.working_directory)
92
111
  end
93
112
  end
94
113
  end
@@ -141,12 +160,15 @@ module Match
141
160
 
142
161
  if certs.count == 0 || keys.count == 0
143
162
  UI.important("Couldn't find a valid code signing identity for #{cert_type}... creating one for you now")
144
- UI.crash!("No code signing identity found and can not create a new one because you enabled `readonly`") if params[:readonly]
163
+ UI.crash!("No code signing identity found and cannot create a new one because you enabled `readonly`") if params[:readonly]
145
164
  cert_path = Generator.generate_certificate(params, cert_type, prefixed_working_directory, specific_cert_type: specific_cert_type)
146
165
  private_key_path = cert_path.gsub(".cer", ".p12")
147
166
 
148
167
  self.files_to_commit << cert_path
149
168
  self.files_to_commit << private_key_path
169
+
170
+ # Reset certificates cache since we have a new cert.
171
+ self.cache.reset_certificates
150
172
  else
151
173
  cert_path = select_cert_or_key(paths: certs)
152
174
 
@@ -199,7 +221,7 @@ module Match
199
221
 
200
222
  # rubocop:disable Metrics/PerceivedComplexity
201
223
  # @return [String] The UUID of the provisioning profile so we can verify it with the Apple Developer Portal
202
- def fetch_provisioning_profile(params: nil, certificate_id: nil, app_identifier: nil, working_directory: nil)
224
+ def fetch_provisioning_profile(params: nil, profile_type:, certificate_id: nil, app_identifier: nil, working_directory: nil)
203
225
  prov_type = Match.profile_type_sym(params[:type])
204
226
 
205
227
  names = [Match::Generator.profile_type_name(prov_type), app_identifier]
@@ -222,20 +244,26 @@ module Match
222
244
  end
223
245
 
224
246
  # Install the provisioning profiles
225
- profile = profiles.last
247
+ stored_profile_path = profiles.last
226
248
  force = params[:force]
227
249
 
228
- if params[:force_for_new_devices]
229
- force = should_force_include_all_devices(params: params, prov_type: prov_type, profile: profile, keychain_path: keychain_path) unless force
230
- end
250
+ if spaceship
251
+ # check if profile needs to be updated only if not in readonly mode
252
+ portal_profile = self.cache.portal_profile(stored_profile_path: stored_profile_path, keychain_path: keychain_path) if stored_profile_path
231
253
 
232
- if params[:include_all_certificates]
233
- # Clearing specified certificate id which will prevent a profile being created with only one certificate
234
- certificate_id = nil
235
- force = should_force_include_all_certificates(params: params, prov_type: prov_type, profile: profile, keychain_path: keychain_path) unless force
254
+ if params[:force_for_new_devices]
255
+ force ||= ProfileIncludes.should_force_include_all_devices?(params: params, portal_profile: portal_profile, cached_devices: self.cache.devices)
256
+ end
257
+
258
+ if params[:include_all_certificates]
259
+ # Clearing specified certificate id which will prevent a profile being created with only one certificate
260
+ certificate_id = nil
261
+ force ||= ProfileIncludes.should_force_include_all_certificates?(params: params, portal_profile: portal_profile, cached_certificates: self.cache.certificates)
262
+ end
236
263
  end
237
264
 
238
- if profile.nil? || force
265
+ is_new_profile_created = false
266
+ if stored_profile_path.nil? || force
239
267
  if params[:readonly]
240
268
  UI.error("No matching provisioning profiles found for '#{profile_file}'")
241
269
  UI.error("A new one cannot be created because you enabled `readonly`")
@@ -245,38 +273,53 @@ module Match
245
273
  all_profiles.each { |p| UI.error("- '#{p}'") }
246
274
  end
247
275
  UI.error("If you are certain that a profile should exist, double-check the recent changes to your match repository")
248
- UI.user_error!("No matching provisioning profiles found and can not create a new one because you enabled `readonly`. Check the output above for more information.")
276
+ UI.user_error!("No matching provisioning profiles found and cannot create a new one because you enabled `readonly`. Check the output above for more information.")
249
277
  end
250
278
 
251
- profile = Generator.generate_provisioning_profile(params: params,
252
- prov_type: prov_type,
253
- certificate_id: certificate_id,
254
- app_identifier: app_identifier,
255
- force: force,
256
- working_directory: prefixed_working_directory)
257
- self.files_to_commit << profile
258
- end
279
+ stored_profile_path = Generator.generate_provisioning_profile(
280
+ params: params,
281
+ prov_type: prov_type,
282
+ certificate_id: certificate_id,
283
+ app_identifier: app_identifier,
284
+ force: force,
285
+ cache: self.cache,
286
+ working_directory: prefixed_working_directory
287
+ )
259
288
 
260
- if Helper.mac?
261
- installed_profile = FastlaneCore::ProvisioningProfile.install(profile, keychain_path)
289
+ # Recreation of the profile means old profile is invalid.
290
+ # Removing it from cache. We don't need a new profile in cache.
291
+ self.cache.forget_portal_profile(portal_profile) if portal_profile
292
+
293
+ self.files_to_commit << stored_profile_path
294
+
295
+ is_new_profile_created = true
262
296
  end
263
- parsed = FastlaneCore::ProvisioningProfile.parse(profile, keychain_path)
297
+
298
+ parsed = FastlaneCore::ProvisioningProfile.parse(stored_profile_path, keychain_path)
264
299
  uuid = parsed["UUID"]
300
+ name = parsed["Name"]
265
301
 
266
- if params[:output_path]
267
- FileUtils.cp(profile, params[:output_path])
268
- end
302
+ check_profile_existence = !is_new_profile_created && spaceship
303
+ if check_profile_existence && !spaceship.profile_exists(profile_type: profile_type,
304
+ name: name,
305
+ username: params[:username],
306
+ uuid: uuid,
307
+ cached_profiles: self.cache.profiles)
269
308
 
270
- if spaceship && !spaceship.profile_exists(type: prov_type,
271
- username: params[:username],
272
- uuid: uuid,
273
- platform: params[:platform])
274
309
  # This profile is invalid, let's remove the local file and generate a new one
275
- File.delete(profile)
310
+ File.delete(stored_profile_path)
276
311
  # This method will be called again, no need to modify `files_to_commit`
277
312
  return nil
278
313
  end
279
314
 
315
+ if Helper.mac?
316
+ installed_profile = FastlaneCore::ProvisioningProfile.install(stored_profile_path, keychain_path)
317
+ end
318
+
319
+ if params[:output_path]
320
+ FileUtils.cp(stored_profile_path, params[:output_path])
321
+ end
322
+
280
323
  Utils.fill_environment(Utils.environment_variable_name(app_identifier: app_identifier,
281
324
  type: prov_type,
282
325
  platform: params[:platform]),
@@ -308,145 +351,6 @@ module Match
308
351
  return uuid
309
352
  end
310
353
  # rubocop:enable Metrics/PerceivedComplexity
311
-
312
- def should_force_include_all_devices(params: nil, prov_type: nil, profile: nil, keychain_path: nil)
313
- return false unless params[:force_for_new_devices] && !params[:readonly]
314
-
315
- force = false
316
-
317
- prov_types_without_devices = [:appstore, :developer_id]
318
- if !prov_types_without_devices.include?(prov_type) && !params[:force]
319
- force = device_count_different?(profile: profile, keychain_path: keychain_path, platform: params[:platform].to_sym, include_mac_in_profiles: params[:include_mac_in_profiles])
320
- else
321
- # App Store provisioning profiles don't contain device identifiers and
322
- # thus shouldn't be renewed if the device count has changed.
323
- UI.important("Warning: `force_for_new_devices` is set but is ignored for App Store & Developer ID provisioning profiles.")
324
- UI.important("You can safely stop specifying `force_for_new_devices` when running Match for type 'appstore' or 'developer_id'.")
325
- end
326
-
327
- return force
328
- end
329
-
330
- def device_count_different?(profile: nil, keychain_path: nil, platform: nil, include_mac_in_profiles: false)
331
- return false unless profile
332
-
333
- parsed = FastlaneCore::ProvisioningProfile.parse(profile, keychain_path)
334
- uuid = parsed["UUID"]
335
-
336
- all_profiles = Spaceship::ConnectAPI::Profile.all(includes: "devices")
337
- portal_profile = all_profiles.detect { |i| i.uuid == uuid }
338
-
339
- if portal_profile
340
- profile_device_count = portal_profile.devices.count
341
-
342
- device_classes =
343
- case platform
344
- when :ios
345
- [
346
- Spaceship::ConnectAPI::Device::DeviceClass::IPAD,
347
- Spaceship::ConnectAPI::Device::DeviceClass::IPHONE,
348
- Spaceship::ConnectAPI::Device::DeviceClass::IPOD,
349
- Spaceship::ConnectAPI::Device::DeviceClass::APPLE_WATCH
350
- ]
351
- when :tvos
352
- [
353
- Spaceship::ConnectAPI::Device::DeviceClass::APPLE_TV
354
- ]
355
- when :macos, :catalyst
356
- [
357
- Spaceship::ConnectAPI::Device::DeviceClass::MAC
358
- ]
359
- else
360
- []
361
- end
362
- if platform == :ios && include_mac_in_profiles
363
- device_classes += [Spaceship::ConnectAPI::Device::DeviceClass::APPLE_SILICON_MAC]
364
- end
365
-
366
- devices = Spaceship::ConnectAPI::Device.all
367
- unless device_classes.empty?
368
- devices = devices.select do |device|
369
- device_classes.include?(device.device_class) && device.enabled?
370
- end
371
- end
372
-
373
- portal_device_count = devices.size
374
-
375
- return portal_device_count != profile_device_count
376
- end
377
- return false
378
- end
379
-
380
- def should_force_include_all_certificates(params: nil, prov_type: nil, profile: nil, keychain_path: nil)
381
- unless params[:include_all_certificates]
382
- if params[:force_for_new_certificates]
383
- UI.important("You specified 'force_for_new_certificates: true', but new certificates will not be added, cause 'include_all_certificates' is 'false'")
384
- end
385
- return false
386
- end
387
-
388
- force = false
389
-
390
- if params[:force_for_new_certificates] && !params[:readonly]
391
- if prov_type == :development && !params[:force]
392
- force = certificate_count_different?(profile: profile, keychain_path: keychain_path, platform: params[:platform].to_sym)
393
- else
394
- # All other (not development) provisioning profiles don't contain
395
- # multiple certificates, thus shouldn't be renewed
396
- # if the certificates count has changed.
397
- UI.important("Warning: `force_for_new_certificates` is set but is ignored for non-'development' provisioning profiles.")
398
- UI.important("You can safely stop specifying `force_for_new_certificates` when running Match for '#{prov_type}' provisioning profiles.")
399
- end
400
- end
401
-
402
- return force
403
- end
404
-
405
- def certificate_count_different?(profile: nil, keychain_path: nil, platform: nil)
406
- return false unless profile
407
-
408
- parsed = FastlaneCore::ProvisioningProfile.parse(profile, keychain_path)
409
- uuid = parsed["UUID"]
410
-
411
- all_profiles = Spaceship::ConnectAPI::Profile.all(includes: "certificates")
412
- portal_profile = all_profiles.detect { |i| i.uuid == uuid }
413
-
414
- return false unless portal_profile
415
-
416
- # When a certificate expires (not revoked) provisioning profile stays valid.
417
- # And if we regenerate certificate count will not differ:
418
- # * For portal certificates, we filter out the expired one but includes a new certificate;
419
- # * Profile still contains an expired certificate and is valid.
420
- # Thus, we need to check the validity of profile certificates too.
421
- profile_certs_count = portal_profile.certificates.select(&:valid?).count
422
-
423
- certificate_types =
424
- case platform
425
- when :ios, :tvos
426
- [
427
- Spaceship::ConnectAPI::Certificate::CertificateType::DEVELOPMENT,
428
- Spaceship::ConnectAPI::Certificate::CertificateType::IOS_DEVELOPMENT
429
- ]
430
- when :macos, :catalyst
431
- [
432
- Spaceship::ConnectAPI::Certificate::CertificateType::DEVELOPMENT,
433
- Spaceship::ConnectAPI::Certificate::CertificateType::MAC_APP_DEVELOPMENT
434
- ]
435
- else
436
- []
437
- end
438
-
439
- certificates = Spaceship::ConnectAPI::Certificate.all
440
- unless certificate_types.empty?
441
- certificates = certificates.select do |certificate|
442
- certificate_types.include?(certificate.certificateType) && certificate.valid?
443
- end
444
- end
445
-
446
- portal_certs_count = certificates.size
447
-
448
- return portal_certs_count != profile_certs_count
449
- end
450
354
  end
451
355
  # rubocop:enable Metrics/ClassLength
452
356
  end