fastlane 2.202.0 → 2.212.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (184) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +98 -98
  3. data/cert/lib/cert/runner.rb +15 -7
  4. data/deliver/lib/deliver/app_screenshot.rb +18 -0
  5. data/deliver/lib/deliver/options.rb +6 -2
  6. data/deliver/lib/deliver/runner.rb +76 -29
  7. data/deliver/lib/deliver/upload_price_tier.rb +3 -1
  8. data/deliver/lib/deliver/upload_screenshots.rb +1 -1
  9. data/fastlane/lib/assets/AppfileTemplate +1 -1
  10. data/fastlane/lib/assets/AppfileTemplate.swift +1 -1
  11. data/fastlane/lib/fastlane/actions/badge.rb +1 -1
  12. data/fastlane/lib/fastlane/actions/changelog_from_git_commits.rb +1 -1
  13. data/fastlane/lib/fastlane/actions/danger.rb +14 -0
  14. data/fastlane/lib/fastlane/actions/docs/build_app.md +5 -5
  15. data/fastlane/lib/fastlane/actions/docs/capture_android_screenshots.md +19 -2
  16. data/fastlane/lib/fastlane/actions/docs/frame_screenshots.md +1 -1
  17. data/fastlane/lib/fastlane/actions/docs/run_tests.md +1 -1
  18. data/fastlane/lib/fastlane/actions/get_version_number.rb +1 -1
  19. data/fastlane/lib/fastlane/actions/git_commit.rb +4 -6
  20. data/fastlane/lib/fastlane/actions/import_certificate.rb +1 -1
  21. data/fastlane/lib/fastlane/actions/pod_lib_lint.rb +1 -1
  22. data/fastlane/lib/fastlane/actions/pod_push.rb +19 -1
  23. data/fastlane/lib/fastlane/actions/read_podspec.rb +1 -1
  24. data/fastlane/lib/fastlane/actions/run_tests.rb +11 -9
  25. data/fastlane/lib/fastlane/actions/setup_ci.rb +13 -4
  26. data/fastlane/lib/fastlane/actions/trainer.rb +2 -2
  27. data/fastlane/lib/fastlane/actions/update_code_signing_settings.rb +14 -4
  28. data/fastlane/lib/fastlane/actions/update_info_plist.rb +1 -1
  29. data/fastlane/lib/fastlane/actions/update_project_provisioning.rb +10 -1
  30. data/fastlane/lib/fastlane/actions/upload_symbols_to_sentry.rb +1 -1
  31. data/fastlane/lib/fastlane/actions/verify_build.rb +1 -1
  32. data/fastlane/lib/fastlane/actions/xcode_install.rb +5 -1
  33. data/fastlane/lib/fastlane/actions/xcode_select.rb +1 -1
  34. data/fastlane/lib/fastlane/actions/xcodebuild.rb +8 -2
  35. data/fastlane/lib/fastlane/actions/xcodes.rb +152 -0
  36. data/fastlane/lib/fastlane/actions/xcversion.rb +10 -15
  37. data/fastlane/lib/fastlane/cli_tools_distributor.rb +5 -0
  38. data/fastlane/lib/fastlane/commands_generator.rb +2 -1
  39. data/fastlane/lib/fastlane/fast_file.rb +18 -5
  40. data/fastlane/lib/fastlane/features.rb +3 -0
  41. data/fastlane/lib/fastlane/helper/xcodes_helper.rb +28 -0
  42. data/fastlane/lib/fastlane/helper/xcversion_helper.rb +0 -9
  43. data/fastlane/lib/fastlane/lane_manager.rb +1 -1
  44. data/fastlane/lib/fastlane/plugins/template/%gem_name%.gemspec.erb +1 -1
  45. data/fastlane/lib/fastlane/plugins/template/.rubocop.yml +5 -1
  46. data/fastlane/lib/fastlane/setup/setup_ios.rb +1 -1
  47. data/fastlane/lib/fastlane/swift_fastlane_api_generator.rb +1 -1
  48. data/fastlane/lib/fastlane/swift_lane_manager.rb +11 -3
  49. data/fastlane/lib/fastlane/swift_runner_upgrader.rb +54 -1
  50. data/fastlane/lib/fastlane/tools.rb +16 -0
  51. data/fastlane/lib/fastlane/version.rb +1 -1
  52. data/fastlane/swift/Actions.swift +1 -1
  53. data/fastlane/swift/Appfile.swift +2 -2
  54. data/fastlane/swift/ArgumentProcessor.swift +1 -1
  55. data/fastlane/swift/Atomic.swift +150 -0
  56. data/fastlane/swift/ControlCommand.swift +1 -1
  57. data/fastlane/swift/Deliverfile.swift +2 -2
  58. data/fastlane/swift/DeliverfileProtocol.swift +8 -4
  59. data/fastlane/swift/Fastlane.swift +363 -184
  60. data/fastlane/swift/FastlaneSwiftRunner/FastlaneSwiftRunner.xcodeproj/project.pbxproj +30 -20
  61. data/fastlane/swift/FastlaneSwiftRunner/FastlaneSwiftRunner.xcodeproj/xcshareddata/xcschemes/FastlaneRunner.xcscheme +1 -1
  62. data/fastlane/swift/Gymfile.swift +2 -2
  63. data/fastlane/swift/GymfileProtocol.swift +7 -3
  64. data/fastlane/swift/LaneFileProtocol.swift +2 -2
  65. data/fastlane/swift/MainProcess.swift +3 -3
  66. data/fastlane/swift/Matchfile.swift +2 -2
  67. data/fastlane/swift/MatchfileProtocol.swift +21 -5
  68. data/fastlane/swift/OptionalConfigValue.swift +1 -1
  69. data/fastlane/swift/Plugins.swift +1 -1
  70. data/fastlane/swift/Precheckfile.swift +2 -2
  71. data/fastlane/swift/PrecheckfileProtocol.swift +3 -3
  72. data/fastlane/swift/RubyCommand.swift +1 -1
  73. data/fastlane/swift/RubyCommandable.swift +1 -1
  74. data/fastlane/swift/Runner.swift +10 -2
  75. data/fastlane/swift/RunnerArgument.swift +1 -1
  76. data/fastlane/swift/Scanfile.swift +2 -2
  77. data/fastlane/swift/ScanfileProtocol.swift +11 -3
  78. data/fastlane/swift/Screengrabfile.swift +2 -2
  79. data/fastlane/swift/ScreengrabfileProtocol.swift +3 -3
  80. data/fastlane/swift/Snapshotfile.swift +2 -2
  81. data/fastlane/swift/SnapshotfileProtocol.swift +4 -4
  82. data/fastlane/swift/SocketClient.swift +9 -5
  83. data/fastlane/swift/SocketClientDelegateProtocol.swift +2 -2
  84. data/fastlane/swift/SocketResponse.swift +1 -1
  85. data/fastlane/swift/formatting/Brewfile.lock.json +42 -24
  86. data/fastlane/swift/main.swift +1 -1
  87. data/fastlane/swift/upgrade_manifest.json +1 -1
  88. data/fastlane_core/README.md +1 -0
  89. data/fastlane_core/lib/fastlane_core/cert_checker.rb +79 -17
  90. data/fastlane_core/lib/fastlane_core/device_manager.rb +5 -1
  91. data/fastlane_core/lib/fastlane_core/itunes_transporter.rb +335 -20
  92. data/fastlane_core/lib/fastlane_core/keychain_importer.rb +1 -0
  93. data/fastlane_core/lib/fastlane_core/project.rb +19 -2
  94. data/fastlane_core/lib/fastlane_core/ui/implementations/shell.rb +4 -2
  95. data/frameit/lib/frameit/device.rb +1 -1
  96. data/frameit/lib/frameit/device_types.rb +9 -0
  97. data/frameit/lib/frameit/frame_downloader.rb +1 -1
  98. data/gym/lib/gym/generators/build_command_generator.rb +1 -0
  99. data/gym/lib/gym/options.rb +7 -0
  100. data/match/lib/match/change_password.rb +2 -0
  101. data/match/lib/match/commands_generator.rb +2 -1
  102. data/match/lib/match/encryption/openssl.rb +1 -1
  103. data/match/lib/match/encryption.rb +3 -0
  104. data/match/lib/match/generator.rb +1 -0
  105. data/match/lib/match/importer.rb +10 -1
  106. data/match/lib/match/migrate.rb +4 -3
  107. data/match/lib/match/module.rb +54 -2
  108. data/match/lib/match/nuke.rb +36 -47
  109. data/match/lib/match/options.rb +22 -1
  110. data/match/lib/match/runner.rb +25 -6
  111. data/match/lib/match/setup.rb +1 -1
  112. data/match/lib/match/spaceship_ensure.rb +4 -2
  113. data/match/lib/match/storage/gitlab/client.rb +102 -0
  114. data/match/lib/match/storage/gitlab/secure_file.rb +65 -0
  115. data/match/lib/match/storage/gitlab_secure_files.rb +182 -0
  116. data/match/lib/match/storage/google_cloud_storage.rb +7 -6
  117. data/match/lib/match/storage/s3_storage.rb +3 -3
  118. data/match/lib/match/storage.rb +4 -0
  119. data/match/lib/match/table_printer.rb +2 -1
  120. data/match/lib/match/utils.rb +15 -2
  121. data/pem/lib/pem/manager.rb +1 -1
  122. data/pilot/lib/pilot/build_manager.rb +33 -13
  123. data/pilot/lib/pilot/options.rb +6 -1
  124. data/scan/lib/scan/detect_values.rb +6 -0
  125. data/scan/lib/scan/options.rb +16 -1
  126. data/scan/lib/scan/runner.rb +33 -14
  127. data/scan/lib/scan/test_command_generator.rb +7 -1
  128. data/sigh/lib/sigh/download_all.rb +14 -2
  129. data/sigh/lib/sigh/module.rb +3 -1
  130. data/sigh/lib/sigh/options.rb +5 -0
  131. data/sigh/lib/sigh/runner.rb +12 -2
  132. data/snapshot/lib/assets/SnapshotHelper.swift +3 -3
  133. data/snapshot/lib/snapshot/options.rb +1 -1
  134. data/snapshot/lib/snapshot/reports_generator.rb +1 -0
  135. data/snapshot/lib/snapshot/simulator_launchers/simulator_launcher_base.rb +9 -2
  136. data/spaceship/lib/spaceship/client.rb +36 -25
  137. data/spaceship/lib/spaceship/connect_api/api_client.rb +10 -5
  138. data/spaceship/lib/spaceship/connect_api/models/actor.rb +26 -0
  139. data/spaceship/lib/spaceship/connect_api/models/app.rb +7 -5
  140. data/spaceship/lib/spaceship/connect_api/models/app_info_localization.rb +5 -0
  141. data/spaceship/lib/spaceship/connect_api/models/app_screenshot_set.rb +5 -0
  142. data/spaceship/lib/spaceship/connect_api/models/app_store_version.rb +1 -1
  143. data/spaceship/lib/spaceship/connect_api/models/app_store_version_localization.rb +27 -10
  144. data/spaceship/lib/spaceship/connect_api/models/build_bundle.rb +9 -0
  145. data/spaceship/lib/spaceship/connect_api/models/build_bundle_file_sizes.rb +34 -0
  146. data/spaceship/lib/spaceship/connect_api/models/build_delivery.rb +2 -1
  147. data/spaceship/lib/spaceship/connect_api/models/certificate.rb +1 -0
  148. data/spaceship/lib/spaceship/connect_api/models/device.rb +47 -4
  149. data/spaceship/lib/spaceship/connect_api/models/profile.rb +4 -0
  150. data/spaceship/lib/spaceship/connect_api/models/resolution_center_message.rb +29 -0
  151. data/spaceship/lib/spaceship/connect_api/models/resolution_center_thread.rb +67 -0
  152. data/spaceship/lib/spaceship/connect_api/models/review_rejection.rb +19 -0
  153. data/spaceship/lib/spaceship/connect_api/models/review_submission.rb +13 -0
  154. data/spaceship/lib/spaceship/connect_api/models/user.rb +5 -0
  155. data/spaceship/lib/spaceship/connect_api/provisioning/provisioning.rb +19 -0
  156. data/spaceship/lib/spaceship/connect_api/response.rb +10 -6
  157. data/spaceship/lib/spaceship/connect_api/testflight/testflight.rb +33 -2
  158. data/spaceship/lib/spaceship/connect_api/token.rb +5 -2
  159. data/spaceship/lib/spaceship/connect_api/tunes/tunes.rb +41 -8
  160. data/spaceship/lib/spaceship/connect_api.rb +6 -0
  161. data/spaceship/lib/spaceship/errors.rb +34 -0
  162. data/spaceship/lib/spaceship/hashcash.rb +52 -0
  163. data/spaceship/lib/spaceship/portal/certificate.rb +4 -3
  164. data/spaceship/lib/spaceship/tunes/app_ratings.rb +6 -6
  165. data/spaceship/lib/spaceship/tunes/iap_families.rb +1 -1
  166. data/spaceship/lib/spaceship/tunes/tunes.rb +0 -1
  167. data/spaceship/lib/spaceship/tunes/tunes_client.rb +79 -21
  168. data/spaceship/lib/spaceship/two_step_or_factor_client.rb +11 -3
  169. data/spaceship/lib/spaceship.rb +1 -0
  170. data/supply/lib/supply/client.rb +2 -7
  171. data/trainer/lib/assets/junit.xml.erb +9 -1
  172. data/trainer/lib/trainer/junit_generator.rb +2 -2
  173. data/trainer/lib/trainer/options.rb +1 -1
  174. data/trainer/lib/trainer/test_parser.rb +25 -3
  175. metadata +36 -33
  176. data/deliver/lib/deliver/.runner.rb.swp +0 -0
  177. data/deliver/lib/deliver/.submit_for_review.rb.swp +0 -0
  178. data/fastlane/lib/.DS_Store +0 -0
  179. data/fastlane/lib/fastlane/.DS_Store +0 -0
  180. data/fastlane/lib/fastlane/actions/.DS_Store +0 -0
  181. data/spaceship/lib/spaceship/connect_api/models/.app.rb.swp +0 -0
  182. data/spaceship/lib/spaceship/connect_api/testflight/.testflight.rb.swp +0 -0
  183. data/spaceship/lib/spaceship/tunes/user_detail.rb +0 -15
  184. data/trainer/lib/.DS_Store +0 -0
@@ -0,0 +1,182 @@
1
+ require 'fastlane_core/command_executor'
2
+ require 'fastlane_core/configuration/configuration'
3
+ require 'net/http/post/multipart'
4
+
5
+ require_relative './gitlab/client'
6
+ require_relative './gitlab/secure_file'
7
+
8
+ require_relative '../options'
9
+ require_relative '../module'
10
+ require_relative '../spaceship_ensure'
11
+ require_relative './interface'
12
+
13
+ module Match
14
+ module Storage
15
+ # Store the code signing identities in GitLab Secure Files
16
+ class GitLabSecureFiles < Interface
17
+ attr_reader :gitlab_client
18
+ attr_reader :project_id
19
+ attr_reader :readonly
20
+ attr_reader :username
21
+ attr_reader :team_id
22
+ attr_reader :team_name
23
+ attr_reader :api_key_path
24
+ attr_reader :api_key
25
+
26
+ def self.configure(params)
27
+ api_v4_url = params[:api_v4_url] || ENV['CI_API_V4_URL'] || 'https://gitlab.com/api/v4'
28
+ project_id = params[:gitlab_project] || ENV['GITLAB_PROJECT'] || ENV['CI_PROJECT_ID']
29
+ job_token = params[:job_token] || ENV['CI_JOB_TOKEN']
30
+ private_token = params[:private_token] || ENV['PRIVATE_TOKEN']
31
+
32
+ if params[:git_url].to_s.length > 0
33
+ UI.important("Looks like you still define a `git_url` somewhere, even though")
34
+ UI.important("you use GitLab Secure Files. You can remove the `git_url`")
35
+ UI.important("from your Matchfile and Fastfile")
36
+ UI.message("The above is just a warning, fastlane will continue as usual now...")
37
+ end
38
+
39
+ return self.new(
40
+ api_v4_url: api_v4_url,
41
+ project_id: project_id,
42
+ job_token: job_token,
43
+ private_token: private_token,
44
+ readonly: params[:readonly],
45
+ username: params[:username],
46
+ team_id: params[:team_id],
47
+ team_name: params[:team_name],
48
+ api_key_path: params[:api_key_path],
49
+ api_key: params[:api_key]
50
+ )
51
+ end
52
+
53
+ def initialize(api_v4_url: nil,
54
+ project_id: nil,
55
+ job_token: nil,
56
+ private_token: nil,
57
+ readonly: nil,
58
+ username: nil,
59
+ team_id: nil,
60
+ team_name: nil,
61
+ api_key_path: nil,
62
+ api_key: nil)
63
+
64
+ @readonly = readonly
65
+ @username = username
66
+ @team_id = team_id
67
+ @team_name = team_name
68
+ @api_key_path = api_key_path
69
+ @api_key = api_key
70
+
71
+ @job_token = job_token
72
+ @private_token = private_token
73
+ @api_v4_url = api_v4_url
74
+ @project_id = project_id
75
+ @gitlab_client = GitLab::Client.new(job_token: job_token, private_token: private_token, project_id: project_id, api_v4_url: api_v4_url)
76
+
77
+ UI.message("Initializing match for GitLab project #{@project_id}")
78
+ end
79
+
80
+ # To make debugging easier, we have a custom exception here
81
+ def prefixed_working_directory
82
+ # We fall back to "*", which means certificates and profiles
83
+ # from all teams that use this bucket would be installed. This is not ideal, but
84
+ # unless the user provides a `team_id`, we can't know which one to use
85
+ # This only happens if `readonly` is activated, and no `team_id` was provided
86
+ @_folder_prefix ||= currently_used_team_id
87
+ if @_folder_prefix.nil?
88
+ # We use a `@_folder_prefix` variable, to keep state between multiple calls of this
89
+ # method, as the value won't change. This way the warning is only printed once
90
+ UI.important("Looks like you run `match` in `readonly` mode, and didn't provide a `team_id`. This will still work, however it is recommended to provide a `team_id` in your Appfile or Matchfile")
91
+ @_folder_prefix = "*"
92
+ end
93
+ return File.join(working_directory, @_folder_prefix)
94
+ end
95
+
96
+ def download
97
+ # Check if we already have a functional working_directory
98
+ return if @working_directory
99
+
100
+ # No existing working directory, creating a new one now
101
+ self.working_directory = Dir.mktmpdir
102
+
103
+ @gitlab_client.files.each do |secure_file|
104
+ secure_file.download(self.working_directory)
105
+ end
106
+
107
+ UI.verbose("Successfully downloaded all Secure Files from GitLab to #{self.working_directory}")
108
+ end
109
+
110
+ def currently_used_team_id
111
+ if self.readonly
112
+ # In readonly mode, we still want to see if the user provided a team_id
113
+ # see `prefixed_working_directory` comments for more details
114
+ return self.team_id
115
+ else
116
+ UI.user_error!("The `team_id` option is required. fastlane cannot automatically determine portal team id via the App Store Connect API (yet)") if self.team_id.to_s.empty?
117
+
118
+ spaceship = SpaceshipEnsure.new(self.username, self.team_id, self.team_name, api_token)
119
+ return spaceship.team_id
120
+ end
121
+ end
122
+
123
+ def api_token
124
+ api_token = Spaceship::ConnectAPI::Token.from(hash: self.api_key, filepath: self.api_key_path)
125
+ api_token ||= Spaceship::ConnectAPI.token
126
+ return api_token
127
+ end
128
+
129
+ # Returns a short string describing + identifing the current
130
+ # storage backend. This will be printed when nuking a storage
131
+ def human_readable_description
132
+ "GitLab Secure Files Storage [#{self.project_id}]"
133
+ end
134
+
135
+ def upload_files(files_to_upload: [], custom_message: nil)
136
+ # `files_to_upload` is an array of files that need to be uploaded to GitLab Secure Files
137
+ # Those doesn't mean they're new, it might just be they're changed
138
+ # Either way, we'll upload them using the same technique
139
+
140
+ files_to_upload.each do |current_file|
141
+ # Go from
142
+ # "/var/folders/px/bz2kts9n69g8crgv4jpjh6b40000gn/T/d20181026-96528-1av4gge/profiles/development/Development_me.mobileprovision"
143
+ # to
144
+ # "profiles/development/Development_me.mobileprovision"
145
+ #
146
+
147
+ # We also remove the trailing `/`
148
+ target_file = current_file.gsub(self.working_directory + "/", "")
149
+ UI.verbose("Uploading '#{target_file}' to GitLab Secure Files...")
150
+ @gitlab_client.upload_file(current_file, target_file)
151
+ end
152
+ end
153
+
154
+ def delete_files(files_to_delete: [], custom_message: nil)
155
+ files_to_delete.each do |current_file|
156
+ target_path = current_file.gsub(self.working_directory + "/", "")
157
+
158
+ secure_file = @gitlab_client.find_file_by_name(target_path)
159
+ UI.message("Deleting '#{target_path}' from GitLab Secure Files...")
160
+ secure_file.delete
161
+ end
162
+ end
163
+
164
+ def skip_docs
165
+ true
166
+ end
167
+
168
+ def list_files(file_name: "", file_ext: "")
169
+ Dir[File.join(working_directory, self.team_id, "**", file_name, "*.#{file_ext}")]
170
+ end
171
+
172
+ # Implement this for the `fastlane match init` command
173
+ # This method must return the content of the Matchfile
174
+ # that should be generated
175
+ def generate_matchfile_content(template: nil)
176
+ project = UI.input("What is your GitLab Project (i.e. gitlab-org/gitlab): ")
177
+
178
+ return "gitlab_project(\"#{project}\")"
179
+ end
180
+ end
181
+ end
182
+ end
@@ -48,7 +48,8 @@ module Match
48
48
  team_id: params[:team_id],
49
49
  team_name: params[:team_name],
50
50
  api_key_path: params[:api_key_path],
51
- api_key: params[:api_key]
51
+ api_key: params[:api_key],
52
+ skip_google_cloud_account_confirmation: params[:skip_google_cloud_account_confirmation]
52
53
  )
53
54
  end
54
55
 
@@ -62,7 +63,8 @@ module Match
62
63
  team_id: nil,
63
64
  team_name: nil,
64
65
  api_key_path: nil,
65
- api_key: nil)
66
+ api_key: nil,
67
+ skip_google_cloud_account_confirmation: nil)
66
68
  @type = type if type
67
69
  @platform = platform if platform
68
70
  @google_cloud_project_id = google_cloud_project_id if google_cloud_project_id
@@ -76,8 +78,7 @@ module Match
76
78
  @api_key_path = api_key_path
77
79
  @api_key = api_key
78
80
 
79
- @google_cloud_keys_file = ensure_keys_file_exists(google_cloud_keys_file, google_cloud_project_id)
80
-
81
+ @google_cloud_keys_file = ensure_keys_file_exists(google_cloud_keys_file, google_cloud_project_id, skip_google_cloud_account_confirmation)
81
82
  if self.google_cloud_keys_file.to_s.length > 0
82
83
  # Extract the Project ID from the `JSON` file
83
84
  # so the user doesn't have to provide it manually
@@ -223,7 +224,7 @@ module Match
223
224
 
224
225
  # This method will make sure the keys file exists
225
226
  # If it's missing, it will help the user set things up
226
- def ensure_keys_file_exists(google_cloud_keys_file, google_cloud_project_id)
227
+ def ensure_keys_file_exists(google_cloud_keys_file, google_cloud_project_id, skip_google_cloud_account_confirmation)
227
228
  if google_cloud_keys_file && File.exist?(google_cloud_keys_file)
228
229
  return google_cloud_keys_file
229
230
  end
@@ -251,7 +252,7 @@ module Match
251
252
  # we can continue and ask the user if they want to use a keys file.
252
253
  end
253
254
 
254
- if application_default_keys && UI.confirm("Do you want to use this system's Google Cloud application default keys?")
255
+ if application_default_keys && (skip_google_cloud_account_confirmation || UI.confirm("Do you want to use this system's Google Cloud application default keys?"))
255
256
  return nil
256
257
  end
257
258
  end
@@ -168,10 +168,10 @@ module Match
168
168
  private
169
169
 
170
170
  def s3_object_path(file_name)
171
- santized = sanitize_file_name(file_name)
172
- return santized if santized.start_with?(s3_object_prefix)
171
+ sanitized = sanitize_file_name(file_name)
172
+ return sanitized if sanitized.start_with?(s3_object_prefix)
173
173
 
174
- s3_object_prefix + santized
174
+ s3_object_prefix + sanitized
175
175
  end
176
176
 
177
177
  def strip_s3_object_prefix(object_path)
@@ -2,6 +2,7 @@ require_relative 'storage/interface'
2
2
  require_relative 'storage/git_storage'
3
3
  require_relative 'storage/google_cloud_storage'
4
4
  require_relative 'storage/s3_storage'
5
+ require_relative 'storage/gitlab_secure_files'
5
6
 
6
7
  module Match
7
8
  module Storage
@@ -16,6 +17,9 @@ module Match
16
17
  },
17
18
  "s3" => lambda { |params|
18
19
  return Storage::S3Storage.configure(params)
20
+ },
21
+ "gitlab_secure_files" => lambda { |params|
22
+ return Storage::GitLabSecureFiles.configure(params)
19
23
  }
20
24
  }
21
25
  end
@@ -33,7 +33,8 @@ module Match
33
33
  Utils.environment_variable_name(app_identifier: app_identifier, type: type, platform: platform) => "Profile UUID",
34
34
  Utils.environment_variable_name_profile_name(app_identifier: app_identifier, type: type, platform: platform) => "Profile Name",
35
35
  Utils.environment_variable_name_profile_path(app_identifier: app_identifier, type: type, platform: platform) => "Profile Path",
36
- Utils.environment_variable_name_team_id(app_identifier: app_identifier, type: type, platform: platform) => "Development Team ID"
36
+ Utils.environment_variable_name_team_id(app_identifier: app_identifier, type: type, platform: platform) => "Development Team ID",
37
+ Utils.environment_variable_name_certificate_name(app_identifier: app_identifier, type: type, platform: platform) => "Certificate Name"
37
38
  }.each do |env_key, name|
38
39
  rows << [name, env_key, ENV[env_key]]
39
40
  end
@@ -31,8 +31,21 @@ module Match
31
31
  (base_environment_variable_name(app_identifier: app_identifier, type: type, platform: platform) + ["profile-path"]).join("_")
32
32
  end
33
33
 
34
- def self.get_cert_info(cer_certificate_path)
35
- cert = OpenSSL::X509::Certificate.new(File.binread(cer_certificate_path))
34
+ def self.environment_variable_name_certificate_name(app_identifier: nil, type: nil, platform: :ios)
35
+ (base_environment_variable_name(app_identifier: app_identifier, type: type, platform: platform) + ["certificate-name"]).join("_")
36
+ end
37
+
38
+ def self.get_cert_info(cer_certificate)
39
+ # can receive a certificate path or the file data
40
+ begin
41
+ if File.exist?(cer_certificate)
42
+ cer_certificate = File.binread(cer_certificate)
43
+ end
44
+ rescue ArgumentError
45
+ # cert strings have null bytes; suppressing output
46
+ end
47
+
48
+ cert = OpenSSL::X509::Certificate.new(cer_certificate)
36
49
 
37
50
  # openssl output:
38
51
  # subject= /UID={User ID}/CN={Certificate Name}/OU={Certificate User}/O={Organisation}/C={Country}
@@ -81,7 +81,7 @@ module PEM
81
81
  p12_cert_path = File.join(output_path, "#{filename_base}.p12")
82
82
  p12_password = PEM.config[:p12_password] == "" ? nil : PEM.config[:p12_password]
83
83
  p12 = OpenSSL::PKCS12.create(p12_password, certificate_type, pkey, x509_certificate)
84
- File.write(p12_cert_path, p12.to_der)
84
+ File.write(p12_cert_path, p12.to_der.force_encoding("UTF-8"))
85
85
  UI.message("p12 certificate: ".green + Pathname.new(p12_cert_path).realpath.to_s)
86
86
  end
87
87
 
@@ -18,6 +18,11 @@ module Pilot
18
18
 
19
19
  UI.user_error!("No ipa or pkg file given") if config[:ipa].nil? && config[:pkg].nil?
20
20
 
21
+ if config[:ipa] && config[:pkg]
22
+ UI.important("WARNING: Both `ipa` and `pkg` options are defined either explicitly or with default_value (build found in directory)")
23
+ UI.important("Uploading `ipa` is preferred by default. Set `app_platform` to `osx` to force uploading `pkg`")
24
+ end
25
+
21
26
  check_for_changelog_or_whats_new!(options)
22
27
 
23
28
  UI.success("Ready to upload new build to TestFlight (App: #{fetch_app_id})...")
@@ -25,24 +30,29 @@ module Pilot
25
30
  dir = Dir.mktmpdir
26
31
 
27
32
  platform = fetch_app_platform
28
- if options[:ipa]
33
+ ipa_path = options[:ipa]
34
+ if ipa_path && platform != 'osx'
35
+ asset_path = ipa_path
29
36
  package_path = FastlaneCore::IpaUploadPackageBuilder.new.generate(app_id: fetch_app_id,
30
- ipa_path: options[:ipa],
37
+ ipa_path: ipa_path,
31
38
  package_path: dir,
32
39
  platform: platform)
33
40
  else
41
+ pkg_path = options[:pkg]
42
+ asset_path = pkg_path
34
43
  package_path = FastlaneCore::PkgUploadPackageBuilder.new.generate(app_id: fetch_app_id,
35
- pkg_path: options[:pkg],
44
+ pkg_path: pkg_path,
36
45
  package_path: dir,
37
46
  platform: platform)
38
47
  end
39
48
 
40
49
  transporter = transporter_for_selected_team(options)
41
- result = transporter.upload(package_path: package_path, asset_path: options[:ipa] || options[:pkg])
50
+ result = transporter.upload(package_path: package_path, asset_path: asset_path, platform: platform)
42
51
 
43
52
  unless result
44
53
  transporter_errors = transporter.displayable_errors
45
- UI.user_error!("Error uploading ipa file: \n #{transporter_errors}")
54
+ file_type = platform == "osx" ? "pkg" : "ipa"
55
+ UI.user_error!("Error uploading #{file_type} file: \n #{transporter_errors}")
46
56
  end
47
57
 
48
58
  UI.success("Successfully uploaded the new binary to App Store Connect")
@@ -98,10 +108,10 @@ module Pilot
98
108
 
99
109
  def wait_for_build_processing_to_be_complete(return_when_build_appears = false)
100
110
  platform = fetch_app_platform
101
- if config[:ipa]
111
+ if config[:ipa] && platform != "osx" && !config[:distribute_only]
102
112
  app_version = FastlaneCore::IpaFileAnalyser.fetch_app_version(config[:ipa])
103
113
  app_build = FastlaneCore::IpaFileAnalyser.fetch_app_build(config[:ipa])
104
- elsif config[:pkg]
114
+ elsif config[:pkg] && !config[:distribute_only]
105
115
  app_version = FastlaneCore::PkgFileAnalyser.fetch_app_version(config[:pkg])
106
116
  app_build = FastlaneCore::PkgFileAnalyser.fetch_app_build(config[:pkg])
107
117
  else
@@ -379,24 +389,34 @@ module Pilot
379
389
  def transporter_for_selected_team(options)
380
390
  # Use JWT auth
381
391
  api_token = Spaceship::ConnectAPI.token
392
+ api_key = if options[:api_key].nil? && !api_token.nil?
393
+ # Load api key info if user set api_key_path, not api_key
394
+ { key_id: api_token.key_id, issuer_id: api_token.issuer_id, key: api_token.key_raw }
395
+ elsif !options[:api_key].nil?
396
+ api_key = options[:api_key].transform_keys(&:to_sym).dup
397
+ # key is still base 64 style if api_key is loaded from option
398
+ api_key[:key] = Base64.decode64(api_key[:key]) if api_key[:is_key_content_base64]
399
+ api_key
400
+ end
401
+
382
402
  unless api_token.nil?
383
403
  api_token.refresh! if api_token.expired?
384
- return FastlaneCore::ItunesTransporter.new(nil, nil, false, nil, api_token.text)
404
+ return FastlaneCore::ItunesTransporter.new(nil, nil, false, nil, api_token.text, upload: true, api_key: api_key)
385
405
  end
386
406
 
387
407
  # Otherwise use username and password
388
408
  tunes_client = Spaceship::ConnectAPI.client ? Spaceship::ConnectAPI.client.tunes_client : nil
389
409
 
390
- generic_transporter = FastlaneCore::ItunesTransporter.new(options[:username], nil, false, options[:itc_provider])
410
+ generic_transporter = FastlaneCore::ItunesTransporter.new(options[:username], nil, false, options[:itc_provider], upload: true, api_key: api_key)
391
411
  return generic_transporter if options[:itc_provider] || tunes_client.nil?
392
412
  return generic_transporter unless tunes_client.teams.count > 1
393
413
 
394
414
  begin
395
- team = tunes_client.teams.find { |t| t['contentProvider']['contentProviderId'].to_s == tunes_client.team_id }
396
- name = team['contentProvider']['name']
415
+ team = tunes_client.teams.find { |t| t['providerId'].to_s == tunes_client.team_id }
416
+ name = team['name']
397
417
  provider_id = generic_transporter.provider_ids[name]
398
418
  UI.verbose("Inferred provider id #{provider_id} for team #{name}.")
399
- return FastlaneCore::ItunesTransporter.new(options[:username], nil, false, provider_id)
419
+ return FastlaneCore::ItunesTransporter.new(options[:username], nil, false, provider_id, upload: true, api_key: api_key)
400
420
  rescue => ex
401
421
  STDERR.puts(ex.to_s)
402
422
  UI.verbose("Couldn't infer a provider short name for team with id #{tunes_client.team_id} automatically: #{ex}. Proceeding without provider short name.")
@@ -410,7 +430,7 @@ module Pilot
410
430
  # This is where we could add a check to see if encryption is required and has been updated
411
431
  uploaded_build = set_export_compliance_if_needed(uploaded_build, options)
412
432
 
413
- if options[:groups] || options[:distribute_external]
433
+ if options[:submit_beta_review] && (options[:groups] || options[:distribute_external])
414
434
  if uploaded_build.ready_for_beta_submission?
415
435
  uploaded_build.post_beta_app_review_submission
416
436
  else
@@ -324,7 +324,12 @@ module Pilot
324
324
  env_name: "PILOT_REJECT_PREVIOUS_BUILD",
325
325
  description: "Expire previous if it's 'waiting for review'",
326
326
  is_string: false,
327
- default_value: false)
327
+ default_value: false),
328
+ FastlaneCore::ConfigItem.new(key: :submit_beta_review,
329
+ env_name: "PILOT_DISTRIBUTE_EXTERNAL",
330
+ description: "Send the build for a beta review",
331
+ type: Boolean,
332
+ default_value: true)
328
333
  ]
329
334
  end
330
335
  end
@@ -209,6 +209,12 @@ module Scan
209
209
 
210
210
  def self.detect_destination
211
211
  if Scan.config[:destination]
212
+ # No need to show below warnings message(s) for xcode13+, because
213
+ # Apple recommended to have destination in all xcodebuild commands
214
+ # otherwise, Apple will generate warnings in console logs
215
+ # see: https://github.com/fastlane/fastlane/issues/19579
216
+ return if Helper.xcode_at_least?("13.0")
217
+
212
218
  UI.important("It's not recommended to set the `destination` value directly")
213
219
  UI.important("Instead use the other options available in `fastlane scan --help`")
214
220
  UI.important("Using your value '#{Scan.config[:destination]}' for now")
@@ -315,6 +315,11 @@ module Scan
315
315
  env_name: "SCAN_OUTPUT_XCTESTRUN",
316
316
  description: "Should provide additional copy of .xctestrun file (settings.xctestrun) and place in output path?",
317
317
  default_value: false),
318
+ FastlaneCore::ConfigItem.new(key: :result_bundle_path,
319
+ env_name: "SCAN_RESULT_BUNDLE_PATH",
320
+ description: "Custom path for the result bundle, overrides result_bundle",
321
+ type: String,
322
+ optional: true),
318
323
  FastlaneCore::ConfigItem.new(key: :result_bundle,
319
324
  short_option: "-z",
320
325
  env_name: "SCAN_RESULT_BUNDLE",
@@ -328,6 +333,11 @@ module Scan
328
333
  default_value: false),
329
334
 
330
335
  # concurrency
336
+ FastlaneCore::ConfigItem.new(key: :parallel_testing,
337
+ type: Boolean,
338
+ env_name: "SCAN_PARALLEL_TESTING",
339
+ description: "Optionally override the per-target setting in the scheme for running tests in parallel. Equivalent to -parallel-testing-enabled",
340
+ optional: true),
331
341
  FastlaneCore::ConfigItem.new(key: :concurrent_workers,
332
342
  type: Integer,
333
343
  env_name: "SCAN_CONCURRENT_WORKERS",
@@ -517,7 +527,12 @@ module Scan
517
527
  env_name: 'SCAN_NUMBER_OF_RETRIES',
518
528
  description: "The number of times a test can fail",
519
529
  type: Integer,
520
- default_value: 0)
530
+ default_value: 0),
531
+ FastlaneCore::ConfigItem.new(key: :fail_build,
532
+ env_name: "SCAN_FAIL_BUILD",
533
+ description: "Should this step stop the build if the tests fail? Set this to false if you're using trainer",
534
+ type: Boolean,
535
+ default_value: true)
521
536
 
522
537
  ]
523
538
  end
@@ -21,7 +21,7 @@ module Scan
21
21
 
22
22
  def run
23
23
  @xcresults_before_run = find_xcresults_in_derived_data
24
- handle_results(test_app)
24
+ return handle_results(test_app)
25
25
  end
26
26
 
27
27
  def test_app
@@ -175,6 +175,8 @@ module Scan
175
175
 
176
176
  def find_xcresults_in_derived_data
177
177
  derived_data_path = Scan.config[:derived_data_path]
178
+ return [] if derived_data_path.nil? # Swift packages might not have derived data
179
+
178
180
  xcresults_path = File.join(derived_data_path, "Logs", "Test", "*.xcresult")
179
181
  return Dir[xcresults_path]
180
182
  end
@@ -185,7 +187,10 @@ module Scan
185
187
  results = {
186
188
  number_of_tests: 0,
187
189
  number_of_failures: 0,
188
- number_of_retries: 0
190
+ number_of_retries: 0,
191
+ number_of_skipped: 0,
192
+ number_of_tests_excluding_retries: 0,
193
+ number_of_failures_excluding_retries: 0
189
194
  }
190
195
 
191
196
  result_bundle_path = Scan.cache[:result_bundle_path]
@@ -242,8 +247,11 @@ module Scan
242
247
 
243
248
  resulting_paths = Trainer::TestParser.auto_convert(params)
244
249
  resulting_paths.each do |path, data|
245
- results[:number_of_tests] += data[:number_of_tests_excluding_retries]
246
- results[:number_of_failures] += data[:number_of_failures_excluding_retries]
250
+ results[:number_of_tests] += data[:number_of_tests]
251
+ results[:number_of_failures] += data[:number_of_failures]
252
+ results[:number_of_tests_excluding_retries] += data[:number_of_tests_excluding_retries]
253
+ results[:number_of_failures_excluding_retries] += data[:number_of_failures_excluding_retries]
254
+ results[:number_of_skipped] += data[:number_of_skipped] || 0
247
255
  results[:number_of_retries] += data[:number_of_retries]
248
256
  end
249
257
 
@@ -251,13 +259,18 @@ module Scan
251
259
  end
252
260
 
253
261
  def handle_results(tests_exit_status)
254
- return if Scan.config[:build_for_testing]
262
+ copy_simulator_logs
263
+ zip_build_products
264
+ copy_xctestrun
265
+
266
+ return nil if Scan.config[:build_for_testing]
255
267
 
256
268
  results = trainer_test_results
257
269
 
258
270
  number_of_retries = results[:number_of_retries]
259
- number_of_tests = results[:number_of_tests]
260
- number_of_failures = results[:number_of_failures]
271
+ number_of_skipped = results[:number_of_skipped]
272
+ number_of_tests = results[:number_of_tests_excluding_retries]
273
+ number_of_failures = results[:number_of_failures_excluding_retries]
261
274
 
262
275
  SlackPoster.new.run({
263
276
  tests: number_of_tests,
@@ -283,26 +296,32 @@ module Scan
283
296
  title: "Test Results",
284
297
  rows: [
285
298
  ["Number of tests", "#{number_of_tests}#{retries_str}"],
299
+ number_of_skipped > 0 ? ["Number of tests skipped", number_of_skipped] : nil,
286
300
  ["Number of failures", failures_str]
287
- ]
301
+ ].compact
288
302
  }))
289
303
  puts("")
290
304
 
291
- copy_simulator_logs
292
- zip_build_products
293
- copy_xctestrun
294
-
295
305
  if number_of_failures > 0
296
306
  open_report
297
307
 
298
- UI.test_failure!("Tests have failed")
308
+ if Scan.config[:fail_build]
309
+ UI.test_failure!("Tests have failed")
310
+ else
311
+ UI.error("Tests have failed")
312
+ end
299
313
  end
300
314
 
301
315
  unless tests_exit_status == 0
302
- UI.test_failure!("Test execution failed. Exit status: #{tests_exit_status}")
316
+ if Scan.config[:fail_build]
317
+ UI.test_failure!("Test execution failed. Exit status: #{tests_exit_status}")
318
+ else
319
+ UI.error("Test execution failed. Exit status: #{tests_exit_status}")
320
+ end
303
321
  end
304
322
 
305
323
  open_report
324
+ return results
306
325
  end
307
326
 
308
327
  def open_report
@@ -53,8 +53,14 @@ module Scan
53
53
  if config[:use_system_scm] && !options.include?("-scmProvider system")
54
54
  options << "-scmProvider system"
55
55
  end
56
- options << "-resultBundlePath '#{result_bundle_path(true)}'" if config[:result_bundle]
56
+ if config[:result_bundle_path]
57
+ options << "-resultBundlePath '#{config[:result_bundle_path].shellescape}'"
58
+ Scan.cache[:result_bundle_path] = config[:result_bundle_path]
59
+ elsif config[:result_bundle]
60
+ options << "-resultBundlePath '#{result_bundle_path(true)}'"
61
+ end
57
62
  if FastlaneCore::Helper.xcode_at_least?(10)
63
+ options << "-parallel-testing-enabled #{config[:parallel_testing] ? 'YES' : 'NO'}" unless config[:parallel_testing].nil?
58
64
  options << "-parallel-testing-worker-count #{config[:concurrent_workers]}" if config[:concurrent_workers]
59
65
  options << "-maximum-concurrent-test-simulator-destinations #{config[:max_concurrent_simulators]}" if config[:max_concurrent_simulators]
60
66
  options << "-disable-concurrent-testing" if config[:disable_concurrent_testing]
@@ -40,12 +40,26 @@ module Sigh
40
40
  Spaceship::ConnectAPI::Profile::ProfileType::MAC_APP_DEVELOPMENT,
41
41
  Spaceship::ConnectAPI::Profile::ProfileType::MAC_APP_DIRECT
42
42
  ]
43
+
44
+ # As of 2022-06-25, only available with Apple ID auth
45
+ if Spaceship::ConnectAPI.token
46
+ UI.important("Skipping #{Spaceship::ConnectAPI::Profile::ProfileType::MAC_APP_INHOUSE}... only available with Apple ID auth")
47
+ else
48
+ profile_types << Spaceship::ConnectAPI::Profile::ProfileType::MAC_APP_INHOUSE
49
+ end
43
50
  when 'catalyst'
44
51
  profile_types = [
45
52
  Spaceship::ConnectAPI::Profile::ProfileType::MAC_CATALYST_APP_STORE,
46
53
  Spaceship::ConnectAPI::Profile::ProfileType::MAC_CATALYST_APP_DEVELOPMENT,
47
54
  Spaceship::ConnectAPI::Profile::ProfileType::MAC_CATALYST_APP_DIRECT
48
55
  ]
56
+
57
+ # As of 2022-06-25, only available with Apple ID auth
58
+ if Spaceship::ConnectAPI.token
59
+ UI.important("Skipping #{Spaceship::ConnectAPI::Profile::ProfileType::MAC_CATALYST_APP_INHOUSE}... only available with Apple ID auth")
60
+ else
61
+ profile_types << Spaceship::ConnectAPI::Profile::ProfileType::MAC_CATALYST_APP_INHOUSE
62
+ end
49
63
  when 'tvos'
50
64
  profile_types = [
51
65
  Spaceship::ConnectAPI::Profile::ProfileType::TVOS_APP_STORE,
@@ -55,8 +69,6 @@ module Sigh
55
69
  ]
56
70
  end
57
71
 
58
- # Filtering on 'profileType' seems to be undocumented as of 2020-07-30
59
- # but works on both web session and official API
60
72
  profiles = Spaceship::ConnectAPI::Profile.all(filter: { profileType: profile_types.join(",") }, includes: "bundleId")
61
73
  download_profiles(profiles)
62
74
  end
@@ -24,7 +24,9 @@ module Sigh
24
24
  Spaceship::ConnectAPI::Profile::ProfileType::TVOS_APP_ADHOC
25
25
  "AdHoc"
26
26
  when Spaceship::ConnectAPI::Profile::ProfileType::IOS_APP_INHOUSE,
27
- Spaceship::ConnectAPI::Profile::ProfileType::TVOS_APP_INHOUSE
27
+ Spaceship::ConnectAPI::Profile::ProfileType::TVOS_APP_INHOUSE,
28
+ Spaceship::ConnectAPI::Profile::ProfileType::MAC_APP_INHOUSE,
29
+ Spaceship::ConnectAPI::Profile::ProfileType::MAC_CATALYST_APP_INHOUSE
28
30
  "InHouse"
29
31
  when Spaceship::ConnectAPI::Profile::ProfileType::MAC_APP_DIRECT,
30
32
  Spaceship::ConnectAPI::Profile::ProfileType::MAC_CATALYST_APP_DIRECT