fastlane 2.239.0 → 2.240.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 (35) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +98 -98
  3. data/deliver/lib/deliver/runner.rb +5 -4
  4. data/fastlane/lib/fastlane/actions/build_and_upload_to_appetize.rb +3 -1
  5. data/fastlane/lib/fastlane/actions/lcov.rb +3 -1
  6. data/fastlane/lib/fastlane/actions/spaceship_logs.rb +4 -2
  7. data/fastlane/lib/fastlane/actions/swiftlint.rb +6 -3
  8. data/fastlane/lib/fastlane/actions/update_project_provisioning.rb +3 -1
  9. data/fastlane/lib/fastlane/version.rb +1 -1
  10. data/fastlane/swift/Deliverfile.swift +1 -1
  11. data/fastlane/swift/DeliverfileProtocol.swift +1 -1
  12. data/fastlane/swift/Fastlane.swift +38 -2
  13. data/fastlane/swift/Gymfile.swift +1 -1
  14. data/fastlane/swift/GymfileProtocol.swift +8 -1
  15. data/fastlane/swift/Matchfile.swift +1 -1
  16. data/fastlane/swift/MatchfileProtocol.swift +1 -1
  17. data/fastlane/swift/Precheckfile.swift +1 -1
  18. data/fastlane/swift/PrecheckfileProtocol.swift +1 -1
  19. data/fastlane/swift/Scanfile.swift +1 -1
  20. data/fastlane/swift/ScanfileProtocol.swift +8 -1
  21. data/fastlane/swift/Screengrabfile.swift +1 -1
  22. data/fastlane/swift/ScreengrabfileProtocol.swift +1 -1
  23. data/fastlane/swift/Snapshotfile.swift +1 -1
  24. data/fastlane/swift/SnapshotfileProtocol.swift +8 -1
  25. data/fastlane_core/lib/fastlane_core/fastlane_pty.rb +20 -7
  26. data/fastlane_core/lib/fastlane_core/ipa_file_analyser.rb +1 -1
  27. data/fastlane_core/lib/fastlane_core/itunes_transporter.rb +16 -13
  28. data/fastlane_core/lib/fastlane_core/project.rb +19 -0
  29. data/gym/lib/gym/options.rb +5 -0
  30. data/scan/lib/scan/options.rb +5 -0
  31. data/snapshot/lib/snapshot/options.rb +5 -0
  32. data/spaceship/lib/spaceship/client.rb +137 -15
  33. data/spaceship/lib/spaceship/du/upload_file.rb +4 -3
  34. data/trainer/lib/trainer/xcresult/helper.rb +27 -13
  35. metadata +27 -27
@@ -412,6 +412,16 @@ module FastlaneCore
412
412
  # @param [String] The key of which we want the value for (e.g. "PRODUCT_NAME")
413
413
  def build_settings(key: nil, optional: true)
414
414
  unless @build_settings
415
+ if (disallowed_by = xcodebuild_settings_lookup_disallowed_by)
416
+ message = "Could not read the '#{key}' build setting: fetching build settings by running" \
417
+ " `xcodebuild -showBuildSettings` is disallowed by #{disallowed_by}."
418
+ trigger = caller.find { |frame| !frame.start_with?(__FILE__) }
419
+ message += "\nThe build setting lookup was triggered by: #{trigger}" if trigger
420
+ message += "\nTo fix this, manually specify the option whose automatic detection required the '#{key}' build setting," \
421
+ " or disable #{disallowed_by} to allow fastlane to fetch it automatically."
422
+ UI.user_error!(message)
423
+ end
424
+
415
425
  if is_workspace
416
426
  if schemes.count == 0
417
427
  UI.user_error!("Could not find any schemes for Xcode workspace at path '#{self.path}'. Please make sure that the schemes you want to use are marked as `Shared` from Xcode.")
@@ -563,5 +573,14 @@ module FastlaneCore
563
573
  def automated_scheme_selection?
564
574
  FastlaneCore::Env.truthy?("AUTOMATED_SCHEME_SELECTION")
565
575
  end
576
+
577
+ # Returns a description of what disallowed fetching build settings via the
578
+ # (potentially slow on large projects) `xcodebuild -showBuildSettings` command,
579
+ # or nil if the lookup is allowed
580
+ def xcodebuild_settings_lookup_disallowed_by
581
+ return "the FASTLANE_DISALLOW_XCODEBUILD_SETTINGS_LOOKUP environment variable" if FastlaneCore::Env.truthy?("FASTLANE_DISALLOW_XCODEBUILD_SETTINGS_LOOKUP")
582
+ return "the `disallow_xcodebuild_settings_lookup` option" if options[:disallow_xcodebuild_settings_lookup]
583
+ nil
584
+ end
566
585
  end
567
586
  end
@@ -321,6 +321,11 @@ module Gym
321
321
  description: "Skips resolution of Swift Package Manager dependencies",
322
322
  type: Boolean,
323
323
  default_value: false),
324
+ FastlaneCore::ConfigItem.new(key: :disallow_xcodebuild_settings_lookup,
325
+ env_name: "GYM_DISALLOW_XCODEBUILD_SETTINGS_LOOKUP",
326
+ description: "Raises an error instead of fetching build settings by running `xcodebuild -showBuildSettings`, which can take a long time on large projects. The error names the required build setting, so the corresponding option can be specified manually",
327
+ type: Boolean,
328
+ default_value: false),
324
329
  FastlaneCore::ConfigItem.new(key: :disable_package_automatic_updates,
325
330
  env_name: "GYM_DISABLE_PACKAGE_AUTOMATIC_UPDATES",
326
331
  description: "Prevents packages from automatically being resolved to versions other than those recorded in the `Package.resolved` file. This translates in the option `-disableAutomaticPackageResolution` being passed to xcodebuild",
@@ -527,6 +527,11 @@ module Scan
527
527
  description: "Skips resolution of Swift Package Manager dependencies",
528
528
  type: Boolean,
529
529
  default_value: false),
530
+ FastlaneCore::ConfigItem.new(key: :disallow_xcodebuild_settings_lookup,
531
+ env_name: "SCAN_DISALLOW_XCODEBUILD_SETTINGS_LOOKUP",
532
+ description: "Raises an error instead of fetching build settings by running `xcodebuild -showBuildSettings`, which can take a long time on large projects. The error names the required build setting, so the corresponding option can be specified manually",
533
+ type: Boolean,
534
+ default_value: false),
530
535
  FastlaneCore::ConfigItem.new(key: :disable_package_automatic_updates,
531
536
  env_name: "SCAN_DISABLE_PACKAGE_AUTOMATIC_UPDATES",
532
537
  description: "Prevents packages from automatically being resolved to versions other than those recorded in the `Package.resolved` file. This translates in the option `-disableAutomaticPackageResolution` being passed to xcodebuild",
@@ -268,6 +268,11 @@ module Snapshot
268
268
  description: "Skips resolution of Swift Package Manager dependencies",
269
269
  type: Boolean,
270
270
  default_value: false),
271
+ FastlaneCore::ConfigItem.new(key: :disallow_xcodebuild_settings_lookup,
272
+ env_name: "SNAPSHOT_DISALLOW_XCODEBUILD_SETTINGS_LOOKUP",
273
+ description: "Raises an error instead of fetching build settings by running `xcodebuild -showBuildSettings`, which can take a long time on large projects. The error names the required build setting, so the corresponding option can be specified manually",
274
+ type: Boolean,
275
+ default_value: false),
271
276
  FastlaneCore::ConfigItem.new(key: :disable_package_automatic_updates,
272
277
  env_name: "SNAPSHOT_DISABLE_PACKAGE_AUTOMATIC_UPDATES",
273
278
  description: "Prevents packages from automatically being resolved to versions other than those recorded in the `Package.resolved` file. This translates in the option `-disableAutomaticPackageResolution` being passed to xcodebuild",
@@ -37,7 +37,7 @@ module Spaceship
37
37
  attr_accessor :user_email
38
38
 
39
39
  # The logger in which all requests are logged
40
- # /tmp/spaceship[time]_[pid].log by default
40
+ # <tmpdir>/spaceship[time]_[pid].log by default
41
41
  attr_accessor :logger
42
42
 
43
43
  attr_accessor :csrf_tokens
@@ -239,14 +239,19 @@ module Spaceship
239
239
  #####################################################
240
240
 
241
241
  # The logger in which all requests are logged
242
- # /tmp/spaceship[time]_[pid]_["threadid"].log by default
242
+ # <tmpdir>/spaceship[time]_[pid]_["threadid"].log by default
243
+ #
244
+ # Dir.tmpdir rather than a literal "/tmp", and it should stay that way:
245
+ # "/tmp" is not a directory on every platform Ruby runs on. On Windows it
246
+ # resolves against the current drive, so Logger.new raises Errno::ENOENT
247
+ # unless something else happens to have created it first.
243
248
  def logger
244
249
  unless @logger
245
250
  if ENV["VERBOSE"]
246
251
  @logger = Logger.new(STDOUT)
247
252
  else
248
253
  # Log to file by default
249
- path = "/tmp/spaceship#{Time.now.to_i}_#{Process.pid}_#{Thread.current.object_id}.log"
254
+ path = File.join(Dir.tmpdir, "spaceship#{Time.now.to_i}_#{Process.pid}_#{Thread.current.object_id}.log")
250
255
  @logger = Logger.new(path)
251
256
  end
252
257
 
@@ -716,28 +721,145 @@ module Spaceship
716
721
  exit(has_valid_session)
717
722
  end
718
723
 
724
+ # <tmpdir>/spaceship_itc_service_key.txt
725
+ #
726
+ # Dir.tmpdir rather than a literal "/tmp", for the same reason as #logger,
727
+ # and here the failure was worse than a missing file: itc_service_key
728
+ # rescues everything, so the write failing on Windows was reported as an
729
+ # App Store Connect outage. See #30198.
730
+ #
731
+ # On macOS this is also per user, so a cache written by one user no longer
732
+ # blocks another.
733
+ def itc_service_key_path
734
+ File.join(Dir.tmpdir, "spaceship_itc_service_key.txt")
735
+ end
736
+
719
737
  def itc_service_key
720
738
  return @service_key if @service_key
721
739
 
722
- # Check if we have a local cache of the key
723
- itc_service_key_path = "/tmp/spaceship_itc_service_key.txt"
724
- return File.read(itc_service_key_path) if File.exist?(itc_service_key_path)
740
+ # Check if we have a local cache of the key. Reading it is best effort:
741
+ # an unreadable cache is a reason to fetch the key again, not to fail.
742
+ begin
743
+ return File.read(itc_service_key_path) if File.exist?(itc_service_key_path)
744
+ rescue SystemCallError => ex
745
+ logger.warn("Could not read the cached App Store Connect API key: #{ex.message}")
746
+ end
747
+
748
+ @service_key = fetch_service_key
749
+
750
+ # Cache the key locally. Best effort as well: having the key and failing
751
+ # to save it is not a reason to fail the login. See fastlane#30198.
752
+ begin
753
+ File.write(itc_service_key_path, @service_key)
754
+ rescue SystemCallError => ex
755
+ logger.warn("Could not cache the App Store Connect API key at #{itc_service_key_path}: #{ex.message}")
756
+ end
757
+
758
+ return @service_key
759
+ end
760
+
761
+ # Two sources, because the one this used to rely on is gone.
762
+ #
763
+ # App Store Connect's sign out route answers with a redirect that carries
764
+ # the key its own front end is using:
765
+ #
766
+ # GET /logout -> 302 Location: .../appleauth/signout?widgetKey=<key>&...
767
+ #
768
+ # That is preferred over the olympus endpoint below, which Apple removed in
769
+ # September 2026 and which now answers 404. See fastlane#30199. The olympus
770
+ # call is kept as a fallback in case it comes back, since it is the source
771
+ # Apple documented rather than one scraped out of a redirect.
772
+ def fetch_service_key
773
+ key = fetch_service_key_from_signout
774
+ return key if key
725
775
 
726
776
  # Fixes issue https://github.com/fastlane/fastlane/issues/13281
727
777
  # Even though we are using https://appstoreconnect.apple.com, the service key needs to still use a
728
778
  # hostname through itunesconnect.apple.com
729
- response = request(:get, "https://appstoreconnect.apple.com/olympus/v1/app/config?hostname=itunesconnect.apple.com")
730
- @service_key = response.body["authServiceKey"].to_s
779
+ response = begin
780
+ request(:get, "https://appstoreconnect.apple.com/olympus/v1/app/config?hostname=itunesconnect.apple.com")
781
+ rescue Faraday::TimeoutError, Faraday::ConnectionFailed => ex
782
+ # The only case the old message was ever right about.
783
+ raise AppleTimeoutError.new, "Could not reach App Store Connect to fetch the API key: #{ex.message}"
784
+ end
731
785
 
732
- raise "Service key is empty" if @service_key.length == 0
786
+ extract_service_key(response)
787
+ end
733
788
 
734
- # Cache the key locally
735
- File.write(itc_service_key_path, @service_key)
789
+ # Read the key out of the sign out redirect, or nil if it is not there.
790
+ #
791
+ # Two things about this request matter, and neither is decoration.
792
+ #
793
+ # It sends no cookies. The redirect it returns points at a signout with
794
+ # `asop=destroy-session`, so asking for it over the client's own connection,
795
+ # which carries the session jar, would end the session this method is being
796
+ # called in order to establish. A bare Faraday connection has neither a
797
+ # cookie jar nor redirect following, which is exactly what is wanted here.
798
+ #
799
+ # It does not follow the redirect. The key is in the Location header;
800
+ # following it is what performs the signout.
801
+ #
802
+ # Failure returns nil rather than raising, so the caller falls through to
803
+ # the other source rather than this becoming a new single point of failure.
804
+ # Deliberately not `self.class.client` or anything built from it. A bare
805
+ # Faraday connection has no cookie jar and no redirect following, which is
806
+ # the whole point; the spec asserts both rather than trusting this comment.
807
+ def signout_connection
808
+ Faraday.new(url: "https://appstoreconnect.apple.com")
809
+ end
736
810
 
737
- return @service_key
738
- rescue => ex
739
- puts(ex.to_s)
740
- raise AppleTimeoutError.new, "Could not receive latest API key from App Store Connect, this might be a server issue."
811
+ def fetch_service_key_from_signout
812
+ response = signout_connection.head("/logout")
813
+
814
+ location = response.headers["location"].to_s
815
+ return nil if location.empty?
816
+
817
+ query = URI.parse(location).query
818
+ return nil if query.nil?
819
+
820
+ key = CGI.parse(query)["widgetKey"].first.to_s
821
+ return nil if key.empty?
822
+
823
+ logger.debug("Read the App Store Connect API key from the sign out redirect")
824
+ key
825
+ rescue Faraday::Error, URI::InvalidURIError => ex
826
+ # Only what this request can be expected to go wrong with. Rescuing
827
+ # everything here would put back the problem fastlane#30198 is about: a
828
+ # local failure, a missing log directory being the one that started it,
829
+ # would be swallowed and the caller would be handed whatever the fallback
830
+ # said instead of the real cause.
831
+ #
832
+ # Warn rather than debug. This is the source the key normally comes from,
833
+ # so failing here means the run is about to depend on an endpoint Apple has
834
+ # already removed once. If the fallback fails too, its message is all the
835
+ # caller sees, and this line is the half that says why.
836
+ logger.warn("Could not read the App Store Connect API key from the sign out redirect, falling back to the olympus endpoint: #{ex.message}")
837
+ nil
838
+ end
839
+
840
+ # The status matters, and used to be thrown away. A non 2xx response body is
841
+ # an HTML error page rather than JSON, and String#[] on it returns nil for
842
+ # the key being looked up, so the failure presented as an empty key no
843
+ # matter what had actually gone wrong. See fastlane#30199.
844
+ def extract_service_key(response)
845
+ status = response.status.to_i
846
+ body = response.body
847
+
848
+ unless (200..299).cover?(status)
849
+ detail = body.to_s.strip[0, 200]
850
+ message = "App Store Connect returned #{status} for the API key endpoint."
851
+ message += " #{detail}" unless detail.empty?
852
+
853
+ # 5xx and 429 are worth retrying, a 4xx is not, and with_retry decides
854
+ # that from the class rather than the message.
855
+ raise AppleTimeoutError.new, "#{message} This might be a temporary server error, check https://developer.apple.com/system-status/" if status >= 500
856
+ raise UnexpectedResponse.new, message
857
+ end
858
+
859
+ key = body.kind_of?(Hash) ? body["authServiceKey"].to_s : ""
860
+ raise UnexpectedResponse.new, "App Store Connect returned #{status} for the API key endpoint but no authServiceKey in the response." if key.empty?
861
+
862
+ key
741
863
  end
742
864
 
743
865
  #####################################################
@@ -1,3 +1,4 @@
1
+ require 'tmpdir'
1
2
  require 'fileutils'
2
3
 
3
4
  require_relative 'utilities'
@@ -30,10 +31,10 @@ module Spaceship
30
31
  end
31
32
 
32
33
  # As things like screenshots and app icon shouldn't contain the alpha channel
33
- # This will copy the image into /tmp to remove the alpha channel there
34
- # That's done to not edit the original image
34
+ # This will copy the image into the temporary directory to remove the
35
+ # alpha channel there. That's done to not edit the original image.
35
36
  def remove_alpha_channel(original)
36
- path = "/tmp/#{Digest::MD5.hexdigest(original)}.png"
37
+ path = File.join(Dir.tmpdir, "#{Digest::MD5.hexdigest(original)}.png")
37
38
  FileUtils.copy(original, path)
38
39
  if mac? # sips is only available on macOS
39
40
  `sips -s format bmp '#{path}' &> /dev/null` # &> /dev/null since there is warning because of the extension
@@ -32,6 +32,29 @@ module Trainer
32
32
  node['children'].select { |child| node_types.include?(child['nodeType']) }
33
33
  end
34
34
 
35
+ # The version of the xcresulttool on the current PATH
36
+ #
37
+ # Not memoized: `xcrun` resolves through DEVELOPER_DIR, which the xcode_select,
38
+ # xcversion, xcodes and xcode_install actions change during a run, so the answer
39
+ # can differ between two calls in the same process.
40
+ #
41
+ # @return [Gem::Version, nil] The version, or nil when there is no xcrun to ask
42
+ def self.xcresulttool_version
43
+ # e.g. DEVELOPER_DIR=/Applications/Xcode_16_beta_3.app
44
+ # xcresulttool version 23021, format version 3.53 (current)
45
+ # xcresulttool version 24408, schema version: 0.1.0 (legacy commands format version: 3.56)
46
+ output = `xcrun xcresulttool version`
47
+ match = output.match(/xcresulttool version (?<version>[\d.]+)/)
48
+ raise "Could not read the xcresulttool version from #{output.strip.inspect}" if match.nil?
49
+
50
+ Gem::Version.new(match[:version])
51
+ rescue Errno::ENOENT
52
+ # No xcrun at all: not macOS, or no developer tools installed. Every other
53
+ # failure is left to surface, so a change in what xcresulttool reports is not
54
+ # silently read as "this version is not supported".
55
+ nil
56
+ end
57
+
35
58
  # Check if the current xcresulttool supports new commands introduced in Xcode 16+
36
59
  #
37
60
  # Since Xcode 16b3, xcresulttool has marked `get <object> --format json` as deprecated/legacy,
@@ -39,24 +62,15 @@ module Trainer
39
62
  #
40
63
  # @return [Boolean] Whether the xcresulttool supports Xcode 16+ commands
41
64
  def self.supports_xcresulttool_version_23?
42
- # e.g. DEVELOPER_DIR=/Applications/Xcode_16_beta_3.app
43
- # xcresulttool version 23021, format version 3.53 (current)
44
- match = `xcrun xcresulttool version`.match(/xcresulttool version (?<version>[\d.]+)/)
45
- version = match[:version]
65
+ version = xcresulttool_version
46
66
 
47
- Gem::Version.new(version) >= Gem::Version.new(23_021)
48
- rescue
49
- false
67
+ !version.nil? && version >= Gem::Version.new(23_021)
50
68
  end
51
69
 
52
70
  def self.supports_xcresulttool_version_24?
53
- # xcresulttool version 24408, schema version: 0.1.0 (legacy commands format version: 3.56)
54
- match = `xcrun xcresulttool version`.match(/xcresulttool version (?<version>[\d.]+)/)
55
- version = match[:version]
71
+ version = xcresulttool_version
56
72
 
57
- Gem::Version.new(version) >= Gem::Version.new(24_408)
58
- rescue
59
- false
73
+ !version.nil? && version >= Gem::Version.new(24_408)
60
74
  end
61
75
  end
62
76
  end
metadata CHANGED
@@ -1,36 +1,36 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: fastlane
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.239.0
4
+ version: 2.240.0
5
5
  platform: ruby
6
6
  authors:
7
- - Łukasz Grabowski
8
- - Maksym Grebenets
9
- - Jérôme Lacoste
10
- - Stefan Natchev
11
- - Roger Oba
12
- - Aaron Brager
13
- - Luka Mirosevic
14
- - Joshua Liebowitz
15
- - Manu Wallner
16
- - Olivier Halligon
17
7
  - Felix Krause
18
- - Daniel Jankowski
8
+ - Łukasz Grabowski
19
9
  - Jorge Revuelta H
20
- - Satoshi Namai
21
- - Kohki Miki
22
- - Fumiya Nakamura
10
+ - Helmut Januschka
11
+ - Jérôme Lacoste
12
+ - Iulian Onofrei
23
13
  - Max Ott
14
+ - Manish Rathi
24
15
  - Connor Tumbleson
25
16
  - Danielle Tomlinson
26
- - Iulian Onofrei
27
- - Jimmy Dee
28
- - Jan Piotrowski
29
- - Manish Rathi
17
+ - Satoshi Namai
18
+ - Daniel Jankowski
19
+ - Manu Wallner
20
+ - Kohki Miki
21
+ - Matthew Ellis
30
22
  - Josh Holtz
31
23
  - Andrew McBurney
32
- - Helmut Januschka
33
- - Matthew Ellis
24
+ - Jimmy Dee
25
+ - Jan Piotrowski
26
+ - Roger Oba
27
+ - Maksym Grebenets
28
+ - Fumiya Nakamura
29
+ - Aaron Brager
30
+ - Olivier Halligon
31
+ - Joshua Liebowitz
32
+ - Luka Mirosevic
33
+ - Stefan Natchev
34
34
  bindir: bin
35
35
  cert_chain: []
36
36
  date: 1980-01-02 00:00:00.000000000 Z
@@ -579,34 +579,34 @@ dependencies:
579
579
  requirements:
580
580
  - - ">="
581
581
  - !ruby/object:Gem::Version
582
- version: 2.0.0
582
+ version: 3.4.0
583
583
  - - "<"
584
584
  - !ruby/object:Gem::Version
585
- version: 3.0.0
585
+ version: 4.0.0
586
586
  type: :runtime
587
587
  prerelease: false
588
588
  version_requirements: !ruby/object:Gem::Requirement
589
589
  requirements:
590
590
  - - ">="
591
591
  - !ruby/object:Gem::Version
592
- version: 2.0.0
592
+ version: 3.4.0
593
593
  - - "<"
594
594
  - !ruby/object:Gem::Version
595
- version: 3.0.0
595
+ version: 4.0.0
596
596
  - !ruby/object:Gem::Dependency
597
597
  name: security
598
598
  requirement: !ruby/object:Gem::Requirement
599
599
  requirements:
600
600
  - - '='
601
601
  - !ruby/object:Gem::Version
602
- version: 0.1.5
602
+ version: 0.2.0
603
603
  type: :runtime
604
604
  prerelease: false
605
605
  version_requirements: !ruby/object:Gem::Requirement
606
606
  requirements:
607
607
  - - '='
608
608
  - !ruby/object:Gem::Version
609
- version: 0.1.5
609
+ version: 0.2.0
610
610
  - !ruby/object:Gem::Dependency
611
611
  name: simctl
612
612
  requirement: !ruby/object:Gem::Requirement