fastlane 2.13.0 → 2.14.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (55) hide show
  1. checksums.yaml +4 -4
  2. data/credentials_manager/lib/credentials_manager.rb +1 -1
  3. data/fastlane/lib/fastlane/actions/ipa.rb +2 -1
  4. data/fastlane/lib/fastlane/actions/mailgun.rb +15 -2
  5. data/fastlane/lib/fastlane/actions/scan.rb +14 -0
  6. data/fastlane/lib/fastlane/documentation/docs_generator.rb +24 -1
  7. data/fastlane/lib/fastlane/environment_printer.rb +2 -1
  8. data/fastlane/lib/fastlane/fast_file.rb +4 -4
  9. data/fastlane/lib/fastlane/version.rb +1 -1
  10. data/fastlane_core/lib/fastlane_core.rb +1 -1
  11. data/fastlane_core/lib/fastlane_core/configuration/config_item.rb +44 -2
  12. data/fastlane_core/lib/fastlane_core/device_manager.rb +15 -0
  13. data/fastlane_core/lib/fastlane_core/helper.rb +1 -1
  14. data/fastlane_core/lib/fastlane_core/ui/disable_colors.rb +4 -4
  15. data/frameit/lib/frameit/config_parser.rb +8 -13
  16. data/frameit/lib/frameit/editor.rb +3 -2
  17. data/gym/lib/gym/options.rb +4 -2
  18. data/match/README.md +2 -2
  19. data/match/lib/match.rb +5 -19
  20. data/match/lib/match/generator.rb +8 -1
  21. data/match/lib/match/git_helper.rb +3 -1
  22. data/match/lib/match/nuke.rb +18 -14
  23. data/match/lib/match/options.rb +13 -2
  24. data/match/lib/match/runner.rb +20 -8
  25. data/match/lib/match/table_printer.rb +5 -4
  26. data/match/lib/match/utils.rb +12 -8
  27. data/scan/lib/scan/options.rb +24 -1
  28. data/scan/lib/scan/runner.rb +12 -11
  29. data/scan/lib/scan/test_command_generator.rb +10 -2
  30. data/sigh/lib/sigh/download_all.rb +1 -1
  31. data/sigh/lib/sigh/runner.rb +2 -2
  32. data/snapshot/lib/snapshot/options.rb +2 -1
  33. data/snapshot/lib/snapshot/runner.rb +12 -12
  34. data/spaceship/lib/spaceship.rb +1 -0
  35. data/spaceship/lib/spaceship/base.rb +27 -4
  36. data/spaceship/lib/spaceship/client.rb +3 -2
  37. data/spaceship/lib/spaceship/du/du_client.rb +26 -16
  38. data/spaceship/lib/spaceship/portal/app.rb +1 -1
  39. data/spaceship/lib/spaceship/portal/person.rb +53 -0
  40. data/spaceship/lib/spaceship/portal/persons.rb +49 -0
  41. data/spaceship/lib/spaceship/portal/portal.rb +2 -0
  42. data/spaceship/lib/spaceship/portal/portal_client.rb +69 -10
  43. data/spaceship/lib/spaceship/portal/provisioning_profile.rb +29 -1
  44. data/spaceship/lib/spaceship/tunes/application.rb +11 -1
  45. data/spaceship/lib/spaceship/tunes/iap.rb +113 -0
  46. data/spaceship/lib/spaceship/tunes/iap_detail.rb +185 -0
  47. data/spaceship/lib/spaceship/tunes/iap_families.rb +62 -0
  48. data/spaceship/lib/spaceship/tunes/iap_family_details.rb +59 -0
  49. data/spaceship/lib/spaceship/tunes/iap_family_list.rb +33 -0
  50. data/spaceship/lib/spaceship/tunes/iap_list.rb +72 -0
  51. data/spaceship/lib/spaceship/tunes/iap_status.rb +48 -0
  52. data/spaceship/lib/spaceship/tunes/iap_type.rb +45 -0
  53. data/spaceship/lib/spaceship/tunes/tunes.rb +1 -0
  54. data/spaceship/lib/spaceship/tunes/tunes_client.rb +163 -1
  55. metadata +21 -5
@@ -46,7 +46,9 @@ module Match
46
46
  [
47
47
  "[fastlane]",
48
48
  "Updated",
49
- params[:type].to_s
49
+ params[:type].to_s,
50
+ "and platform",
51
+ params[:platform]
50
52
  ].join(" ")
51
53
  end
52
54
 
@@ -13,7 +13,7 @@ module Match
13
13
 
14
14
  params[:workspace] = GitHelper.clone(params[:git_url], params[:shallow_clone], skip_docs: params[:skip_docs], branch: params[:git_branch])
15
15
 
16
- had_app_identifier = self.params[:app_identifier]
16
+ had_app_identifier = self.params.fetch(:app_identifier, ask: false)
17
17
  self.params[:app_identifier] = '' # we don't really need a value here
18
18
  FastlaneCore::PrintTable.print_values(config: params,
19
19
  hide_keys: [:app_identifier, :workspace],
@@ -51,12 +51,16 @@ module Match
51
51
  UI.message "Fetching certificates and profiles..."
52
52
  cert_type = Match.cert_type_sym(type)
53
53
 
54
- prov_types = [:development]
54
+ prov_types = []
55
+ prov_types = [:development] if cert_type == :development
55
56
  prov_types = [:appstore, :adhoc] if cert_type == :distribution
57
+ prov_types = [:enterprise] if cert_type == :enterprise
56
58
 
57
59
  Spaceship.login(params[:username])
58
60
  Spaceship.select_team
59
61
 
62
+ UI.user_error!("`fastlane match nuke` doesn't support enterprise accounts") if Spaceship.client.in_house?
63
+
60
64
  self.certs = certificate_type(cert_type).all
61
65
  self.profiles = []
62
66
  prov_types.each do |prov_type|
@@ -170,21 +174,21 @@ module Match
170
174
 
171
175
  # The kind of certificate we're interested in
172
176
  def certificate_type(type)
173
- cert_type = Spaceship.certificate.production
174
- cert_type = Spaceship.certificate.development if type == :development
175
- cert_type = Spaceship.certificate.in_house if Match.enterprise? && Spaceship.client.in_house?
176
-
177
- cert_type
177
+ {
178
+ distribution: Spaceship.certificate.production,
179
+ development: Spaceship.certificate.development,
180
+ enterprise: Spaceship.certificate.in_house
181
+ }[type] ||= raise "Unknown type '#{type}'"
178
182
  end
179
183
 
180
184
  # The kind of provisioning profile we're interested in
181
- def profile_type(type)
182
- profile_type = Spaceship.provisioning_profile.app_store
183
- profile_type = Spaceship.provisioning_profile.in_house if Match.enterprise? && Spaceship.client.in_house?
184
- profile_type = Spaceship.provisioning_profile.ad_hoc if type == :adhoc
185
- profile_type = Spaceship.provisioning_profile.development if type == :development
186
-
187
- profile_type
185
+ def profile_type(prov_type)
186
+ {
187
+ appstore: Spaceship.provisioning_profile.app_store,
188
+ development: Spaceship.provisioning_profile.development,
189
+ enterprise: Spaceship.provisioning_profile.in_house,
190
+ adhoc: Spaceship.provisioning_profile.ad_hoc
191
+ }[prov_type] ||= raise "Unknown provisioning type '#{prov_type}'"
188
192
  end
189
193
  end
190
194
  end
@@ -19,7 +19,7 @@ module Match
19
19
  default_value: 'master'),
20
20
  FastlaneCore::ConfigItem.new(key: :type,
21
21
  env_name: "MATCH_TYPE",
22
- description: "Create a development certificate instead of a distribution one",
22
+ description: "Define the profile type, can be #{Match.environments.join(', ')}",
23
23
  is_string: true,
24
24
  short_option: "-y",
25
25
  default_value: 'development',
@@ -117,7 +117,18 @@ module Match
117
117
  env_name: "MATCH_SKIP_DOCS",
118
118
  description: "Skip generation of a README.md for the created git repository",
119
119
  is_string: false,
120
- default_value: false)
120
+ default_value: false),
121
+ FastlaneCore::ConfigItem.new(key: :platform,
122
+ short_option: '-o',
123
+ env_name: "MATCH_PLATFORM",
124
+ description: "Set the provisioning profile's platform to work with (i.e. ios, tvos)",
125
+ is_string: false,
126
+ default_value: "ios",
127
+ verify_block: proc do |value|
128
+ value = value.to_s
129
+ pt = %w(tvos ios)
130
+ UI.user_error!("Unsupported platform, must be: #{pt}") unless pt.include?(value)
131
+ end)
121
132
  ]
122
133
  end
123
134
  end
@@ -8,10 +8,14 @@ module Match
8
8
  hide_keys: [:workspace],
9
9
  title: "Summary for match #{Fastlane::VERSION}")
10
10
 
11
- UI.error("Enterprise profiles are currently not officially supported in _match_, you might run into issues") if Match.enterprise?
12
-
13
11
  params[:workspace] = GitHelper.clone(params[:git_url], params[:shallow_clone], skip_docs: params[:skip_docs], branch: params[:git_branch])
14
- self.spaceship = SpaceshipEnsure.new(params[:username]) unless params[:readonly]
12
+
13
+ unless params[:readonly]
14
+ self.spaceship = SpaceshipEnsure.new(params[:username])
15
+ if params[:type] == "enterprise" && !Spaceship.client.in_house?
16
+ UI.user_error!("You defined the profile type 'enterprise', but your Apple account doesn't support In-House profiles")
17
+ end
18
+ end
15
19
 
16
20
  if params[:app_identifier].kind_of?(Array)
17
21
  app_identifiers = params[:app_identifier]
@@ -47,7 +51,7 @@ module Match
47
51
 
48
52
  # Print a summary table for each app_identifier
49
53
  app_identifiers.each do |app_identifier|
50
- TablePrinter.print_summary(app_identifier: app_identifier, type: params[:type])
54
+ TablePrinter.print_summary(app_identifier: app_identifier, type: params[:type], platform: params[:platform])
51
55
  end
52
56
 
53
57
  UI.success "All required keys, certificates and provisioning profiles are installed 🙌".green
@@ -98,7 +102,11 @@ module Match
98
102
  def fetch_provisioning_profile(params: nil, certificate_id: nil, app_identifier: nil)
99
103
  prov_type = Match.profile_type_sym(params[:type])
100
104
 
101
- profile_name = [Match::Generator.profile_type_name(prov_type), app_identifier].join("_").gsub("*", '\*') # this is important, as it shouldn't be a wildcard
105
+ names = [Match::Generator.profile_type_name(prov_type), app_identifier]
106
+ if params[:platform].to_s != :ios.to_s
107
+ names.push(params[:platform])
108
+ end
109
+ profile_name = names.join("_").gsub("*", '\*') # this is important, as it shouldn't be a wildcard
102
110
  base_dir = File.join(params[:workspace], "profiles", prov_type.to_s)
103
111
  profiles = Dir[File.join(base_dir, "#{profile_name}.mobileprovision")]
104
112
 
@@ -139,16 +147,20 @@ module Match
139
147
  end
140
148
 
141
149
  Utils.fill_environment(Utils.environment_variable_name(app_identifier: app_identifier,
142
- type: prov_type),
150
+ type: prov_type,
151
+ platform: params[:platform]),
152
+
143
153
  uuid)
144
154
 
145
155
  # TeamIdentifier is returned as an array, but we're not sure why there could be more than one
146
156
  Utils.fill_environment(Utils.environment_variable_name_team_id(app_identifier: app_identifier,
147
- type: prov_type),
157
+ type: prov_type,
158
+ platform: params[:platform]),
148
159
  parsed["TeamIdentifier"].first)
149
160
 
150
161
  Utils.fill_environment(Utils.environment_variable_name_profile_name(app_identifier: app_identifier,
151
- type: prov_type),
162
+ type: prov_type,
163
+ platform: params[:platform]),
152
164
  parsed["Name"])
153
165
 
154
166
  return uuid
@@ -14,18 +14,19 @@ module Match
14
14
  UI.error(ex)
15
15
  end
16
16
 
17
- def self.print_summary(app_identifier: nil, type: nil)
17
+ def self.print_summary(app_identifier: nil, type: nil, platform: :ios)
18
18
  rows = []
19
19
 
20
20
  type = type.to_sym
21
21
 
22
22
  rows << ["App Identifier", "", app_identifier]
23
23
  rows << ["Type", "", type]
24
+ rows << ["Platform", "", platform.to_s]
24
25
 
25
26
  {
26
- Utils.environment_variable_name(app_identifier: app_identifier, type: type) => "Profile UUID",
27
- Utils.environment_variable_name_profile_name(app_identifier: app_identifier, type: type) => "Profile Name",
28
- Utils.environment_variable_name_team_id(app_identifier: app_identifier, type: type) => "Development Team ID"
27
+ Utils.environment_variable_name(app_identifier: app_identifier, type: type, platform: platform) => "Profile UUID",
28
+ Utils.environment_variable_name_profile_name(app_identifier: app_identifier, type: type, platform: platform) => "Profile Name",
29
+ Utils.environment_variable_name_team_id(app_identifier: app_identifier, type: type, platform: platform) => "Development Team ID"
29
30
  }.each do |env_key, name|
30
31
  rows << [name, env_key, ENV[env_key]]
31
32
  end
@@ -11,16 +11,16 @@ module Match
11
11
  ENV[key] = value
12
12
  end
13
13
 
14
- def self.environment_variable_name(app_identifier: nil, type: nil)
15
- base_environment_variable_name(app_identifier: app_identifier, type: type).join("_")
14
+ def self.environment_variable_name(app_identifier: nil, type: nil, platform: :ios)
15
+ base_environment_variable_name(app_identifier: app_identifier, type: type, platform: platform).join("_")
16
16
  end
17
17
 
18
- def self.environment_variable_name_team_id(app_identifier: nil, type: nil)
19
- (base_environment_variable_name(app_identifier: app_identifier, type: type) + ["team-id"]).join("_")
18
+ def self.environment_variable_name_team_id(app_identifier: nil, type: nil, platform: :ios)
19
+ (base_environment_variable_name(app_identifier: app_identifier, type: type, platform: platform) + ["team-id"]).join("_")
20
20
  end
21
21
 
22
- def self.environment_variable_name_profile_name(app_identifier: nil, type: nil)
23
- (base_environment_variable_name(app_identifier: app_identifier, type: type) + ["profile-name"]).join("_")
22
+ def self.environment_variable_name_profile_name(app_identifier: nil, type: nil, platform: :ios)
23
+ (base_environment_variable_name(app_identifier: app_identifier, type: type, platform: platform) + ["profile-name"]).join("_")
24
24
  end
25
25
 
26
26
  def self.get_cert_info(cer_certificate_path)
@@ -52,8 +52,12 @@ module Match
52
52
  return {}
53
53
  end
54
54
 
55
- def self.base_environment_variable_name(app_identifier: nil, type: nil)
56
- ["sigh", app_identifier, type]
55
+ def self.base_environment_variable_name(app_identifier: nil, type: nil, platform: :ios)
56
+ if platform.to_s == :ios.to_s
57
+ ["sigh", app_identifier, type] # We keep the ios profiles without the platform for backwards compatibility
58
+ else
59
+ ["sigh", app_identifier, type, platform.to_s]
60
+ end
57
61
  end
58
62
  end
59
63
  end
@@ -125,6 +125,28 @@ module Scan
125
125
  env_name: "SCAN_FORMATTER",
126
126
  description: "A custom xcpretty formatter to use",
127
127
  optional: true),
128
+
129
+ FastlaneCore::ConfigItem.new(key: :test_without_building,
130
+ short_option: "-T",
131
+ env_name: "SCAN_TEST_WITHOUT_BUILDING",
132
+ description: "Test without building, requires a derrived data path",
133
+ is_string: false,
134
+ conflicting_options: [:build_for_testing],
135
+ optional: true),
136
+ FastlaneCore::ConfigItem.new(key: :build_for_testing,
137
+ short_option: "-B",
138
+ env_name: "SCAN_BUILD_FOR_TESTING",
139
+ description: "Build for testing only, does not run tests",
140
+ conflicting_options: [:test_without_building],
141
+ is_string: false,
142
+ optional: true),
143
+ FastlaneCore::ConfigItem.new(key: :xctestrun,
144
+ short_option: "-X",
145
+ env_name: "SCAN_XCTESTRUN",
146
+ description: "Run tests using the provided .xctestrun file",
147
+ conflicting_options: [:test_without_building, :build_for_testing],
148
+ is_string: true,
149
+ optional: true),
128
150
  FastlaneCore::ConfigItem.new(key: :derived_data_path,
129
151
  short_option: "-j",
130
152
  env_name: "SCAN_DERIVED_DATA_PATH",
@@ -162,7 +184,8 @@ module Scan
162
184
  short_option: "-x",
163
185
  env_name: "SCAN_XCARGS",
164
186
  description: "Pass additional arguments to xcodebuild. Be sure to quote the setting names and values e.g. OTHER_LDFLAGS=\"-ObjC -lstdc++\"",
165
- optional: true),
187
+ optional: true,
188
+ type: :shell_string),
166
189
  FastlaneCore::ConfigItem.new(key: :xcconfig,
167
190
  short_option: "-y",
168
191
  env_name: "SCAN_XCCONFIG",
@@ -16,7 +16,6 @@ module Scan
16
16
  # This way it's okay to just call it for the first simulator we're using for
17
17
  # the first test run
18
18
  open_simulator_for_device(Scan.devices.first) if Scan.devices
19
-
20
19
  command = TestCommandGenerator.generate
21
20
  prefix_hash = [
22
21
  {
@@ -80,17 +79,9 @@ module Scan
80
79
  })
81
80
  puts ""
82
81
 
83
- report_collector.parse_raw_file(TestCommandGenerator.xcodebuild_log_path)
82
+ copy_simulator_logs
84
83
 
85
- if Scan.config[:include_simulator_logs]
86
- Scan.devices.each do |device|
87
- sim_device_logfilepath_source = File.expand_path("~/Library/Logs/CoreSimulator/#{device.udid}/system.log")
88
- if File.exist?(sim_device_logfilepath_source)
89
- sim_device_logfilepath_dest = File.join(Scan.config[:output_directory], "#{device.name}_#{device.os_type}_#{device.os_version}_system.log")
90
- FileUtils.cp(sim_device_logfilepath_source, sim_device_logfilepath_dest)
91
- end
92
- end
93
- end
84
+ report_collector.parse_raw_file(TestCommandGenerator.xcodebuild_log_path)
94
85
 
95
86
  unless tests_exit_status == 0
96
87
  UI.user_error!("Test execution failed. Exit status: #{tests_exit_status}")
@@ -101,6 +92,16 @@ module Scan
101
92
  end
102
93
  end
103
94
 
95
+ def copy_simulator_logs
96
+ return unless Scan.config[:include_simulator_logs]
97
+
98
+ UI.header("Collecting system logs")
99
+ Scan.devices.each do |device|
100
+ logarchive_dest = File.join(Scan.config[:output_directory], "system_logs-#{device.name}_#{device.os_type}_#{device.os_version}.logarchive")
101
+ FastlaneCore::Simulator.copy_logarchive(device, logarchive_dest)
102
+ end
103
+ end
104
+
104
105
  def open_simulator_for_device(device)
105
106
  return unless FastlaneCore::Env.truthy?('FASTLANE_EXPLICIT_OPEN_SIMULATOR')
106
107
 
@@ -39,6 +39,7 @@ module Scan
39
39
  options << "-enableAddressSanitizer #{config[:address_sanitizer] ? 'YES' : 'NO'}" unless config[:address_sanitizer].nil?
40
40
  options << "-enableThreadSanitizer #{config[:thread_sanitizer] ? 'YES' : 'NO'}" unless config[:thread_sanitizer].nil?
41
41
  options << "-xcconfig '#{config[:xcconfig]}'" if config[:xcconfig]
42
+ options << "-xctestrun '#{config[:xctestrun]}'" if config[:xctestrun]
42
43
  options << config[:xcargs] if config[:xcargs]
43
44
 
44
45
  # detect_values will ensure that these values are present as Arrays if
@@ -54,8 +55,15 @@ module Scan
54
55
 
55
56
  actions = []
56
57
  actions << :clean if config[:clean]
57
- actions << :build unless config[:skip_build]
58
- actions << :test
58
+
59
+ if config[:build_for_testing]
60
+ actions << "build-for-testing"
61
+ elsif config[:test_without_building] || config[:xctestrun]
62
+ actions << "test-without-building"
63
+ else
64
+ actions << :build unless config[:skip_build]
65
+ actions << :test
66
+ end
59
67
 
60
68
  actions
61
69
  end
@@ -23,7 +23,7 @@ module Sigh
23
23
  type_name = profile.class.pretty_type
24
24
  type_name = "AdHoc" if profile.is_adhoc?
25
25
 
26
- profile_name = "#{type_name}_#{profile.app.bundle_id}.mobileprovision" # default name
26
+ profile_name = "#{type_name}_#{profile.uuid}_#{profile.app.bundle_id}.mobileprovision" # default name
27
27
 
28
28
  output_path = File.join(Sigh.config[:output_path], profile_name)
29
29
  File.open(output_path, "wb") do |f|
@@ -132,13 +132,13 @@ module Sigh
132
132
  bundle_id: bundle_id,
133
133
  certificate: cert,
134
134
  mac: Sigh.config[:platform].to_s == 'macos',
135
- sub_platform: Sigh.config[:platform].to_s == 'tvos' ? 'tvos' : nil)
135
+ sub_platform: Sigh.config[:platform].to_s == 'tvos' ? 'tvOS' : nil)
136
136
  profile
137
137
  end
138
138
 
139
139
  def certificates_for_profile_and_platform
140
140
  case Sigh.config[:platform].to_s
141
- when 'ios'
141
+ when 'ios', 'tvos'
142
142
  if profile_type == Spaceship.provisioning_profile.Development
143
143
  certificates = Spaceship.certificate.development.all
144
144
  elsif profile_type == Spaceship.provisioning_profile.InHouse
@@ -33,7 +33,8 @@ module Snapshot
33
33
  short_option: "-X",
34
34
  env_name: "SNAPSHOT_XCARGS",
35
35
  description: "Pass additional arguments to xcodebuild for the test phase. Be sure to quote the setting names and values e.g. OTHER_LDFLAGS=\"-ObjC -lstdc++\"",
36
- optional: true),
36
+ optional: true,
37
+ type: :shell_string),
37
38
  FastlaneCore::ConfigItem.new(key: :devices,
38
39
  description: "A list of devices you want to take the screenshots from",
39
40
  short_option: "-d",
@@ -51,6 +51,8 @@ module Snapshot
51
51
  UI.message("snapshot run #{current_run} of #{number_of_runs}")
52
52
 
53
53
  results[device][language] = run_for_device_and_language(language, locale, device, launch_arguments)
54
+
55
+ copy_simulator_logs(device, language, locale, launch_arguments)
54
56
  end
55
57
  end
56
58
  end
@@ -62,10 +64,6 @@ module Snapshot
62
64
  # Generate HTML report
63
65
  ReportsGenerator.new.generate
64
66
 
65
- if Snapshot.config[:output_simulator_logs]
66
- output_simulator_logs
67
- end
68
-
69
67
  # Clear the Derived Data
70
68
  unless Snapshot.config[:derived_data_path]
71
69
  FileUtils.rm_rf(TestCommandGenerator.derived_data_path)
@@ -100,15 +98,17 @@ module Snapshot
100
98
  end
101
99
  end
102
100
 
103
- def output_simulator_logs
104
- Snapshot.config[:devices].each do |device_name|
105
- device = TestCommandGenerator.find_device(device_name)
106
- sim_device_logfilepath_source = File.expand_path("~/Library/Logs/CoreSimulator/#{device.udid}/system.log")
107
- next unless File.exist?(sim_device_logfilepath_source)
101
+ def copy_simulator_logs(device_name, language, locale, launch_arguments)
102
+ return unless Snapshot.config[:output_simulator_logs]
108
103
 
109
- sim_device_logfilepath_dest = File.join(Snapshot.config[:output_directory], "#{device.name}_#{device.os_type}_#{device.os_version}_system.log")
110
- FileUtils.cp(sim_device_logfilepath_source, sim_device_logfilepath_dest)
111
- end
104
+ detected_language = locale || language
105
+ language_folder = File.join(Snapshot.config[:output_directory], detected_language)
106
+ device = TestCommandGenerator.find_device(device_name)
107
+ components = [launch_arguments].delete_if { |a| a.to_s.length == 0 }
108
+
109
+ UI.header("Collecting system logs #{device_name} - #{language}")
110
+ logarchive_dest = File.join(language_folder, "system_logs-" + Digest::MD5.hexdigest(components.join("-")) + ".logarchive")
111
+ FastlaneCore::Simulator.copy_logarchive(device, logarchive_dest)
112
112
  end
113
113
 
114
114
  def print_results(results)
@@ -27,6 +27,7 @@ module Spaceship
27
27
  AppSubmission = Spaceship::Tunes::AppSubmission
28
28
  Application = Spaceship::Tunes::Application
29
29
  Members = Spaceship::Tunes::Members
30
+ Persons = Spaceship::Portal::Persons
30
31
 
31
32
  DESCRIPTION = "Ruby library to access the Apple Dev Center and iTunes Connect".freeze
32
33
  end
@@ -217,20 +217,43 @@ module Spaceship
217
217
  #####################################################
218
218
 
219
219
  def inspect
220
- inspectables = self.attributes
220
+ # To avoid circular references, we keep track of the references
221
+ # of all objects already inspected from the first call to inspect
222
+ # in this call stack
223
+ # We use a Thread local storage for multi-thread friendliness
224
+ thread = Thread.current
225
+ tree_root = thread[:inspected_objects].nil?
226
+ thread[:inspected_objects] = Set.new if tree_root
221
227
 
222
- value = inspectables.map do |k|
228
+ if thread[:inspected_objects].include? self
229
+ # already inspected objects have a default value,
230
+ # let's follow Ruby's convention for circular references
231
+ value = "#<Object ...>"
232
+ else
233
+ thread[:inspected_objects].add self
234
+ begin
235
+ value = inspect_value
236
+ ensure
237
+ thread[:inspected_objects] = nil if tree_root
238
+ end
239
+ end
240
+
241
+ "<#{self.class.name} \n#{value}>"
242
+ end
243
+
244
+ def inspect_value
245
+ self.attributes.map do |k|
223
246
  v = self.send(k).inspect
224
247
  v.gsub!("\n", "\n\t") # to align nested elements
225
248
 
226
249
  "\t#{k}=#{v}"
227
250
  end.join(", \n")
228
-
229
- "<#{self.class.name} \n#{value}>"
230
251
  end
231
252
 
232
253
  def to_s
233
254
  self.inspect
234
255
  end
256
+
257
+ private :inspect_value
235
258
  end
236
259
  end