Class: FastlaneCore::FastlanePty

Inherits:
Object
  • Object
show all
Defined in:
fastlane_core/lib/fastlane_core/fastlane_pty.rb

Class Method Summary collapse

Class Method Details

.process_status ⇒ Object

to ease mocking



92
93
94
# File 'fastlane_core/lib/fastlane_core/fastlane_pty.rb', line 92

def self.process_status
  $?
end

.spawn(command, &block) ⇒ Object



23
24
25
26
27
# File 'fastlane_core/lib/fastlane_core/fastlane_pty.rb', line 23

def self.spawn(command, &block)
  spawn_with_pty(command, &block)
rescue LoadError
  spawn_with_popen(command, &block)
end

.spawn_with_popen(command, &block) ⇒ Object



74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
# File 'fastlane_core/lib/fastlane_core/fastlane_pty.rb', line 74

def self.spawn_with_popen(command, &block)
  status = nil
  require 'open3'
  Open3.popen2e(command) do |command_stdin, command_stdout, p| # note the inversion
    status = p.value
    yield(command_stdout, command_stdin, status.pid)
    command_stdin.close
    command_stdout.close
    raise StandardError, "Process crashed" if status.signaled?
    status.exitstatus
  end
rescue StandardError => e
  # Wrapping any error in FastlanePtyError to allow callers to see and use
  # $?.exitstatus that would usually get returned
  raise FastlanePtyError.new(e, status.exitstatus || e.exit_status, status)
end

.spawn_with_pty(original_command, &block) ⇒ Object



29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
# File 'fastlane_core/lib/fastlane_core/fastlane_pty.rb', line 29

def self.spawn_with_pty(original_command, &block)
  require 'pty'
  # this forces the PTY flush - fixes #21792
  command = ENV['FASTLANE_EXEC_FLUSH_PTY_WORKAROUND'] ? "#{original_command};" : original_command
  # The status is produced in the ensure below and read after the block. It
  # travels in a local rather than in $?, which is a thread global: anything
  # that runs a subprocess in between overwrites it, and nothing writes it at
  # all when the wait does not happen. See fastlane#30188.
  status = nil
  PTY.spawn(command) do |command_stdout, command_stdin, pid|
    begin
      yield(command_stdout, command_stdin, pid)
    rescue Errno::EIO
      # Exception ignored intentionally.
      # https://stackoverflow.com/questions/10238298/ruby-on-linux-pty-goes-away-without-eof-raises-errnoeio
      # This is expected on some linux systems, that indicates that the subcommand finished
      # and we kept trying to read, ignore it
    ensure
      command_stdin.close
      command_stdout.close
      begin
        _pid, status = Process.wait2(pid)
      rescue PTY::ChildExited => e
        # The pty saw the child go before we asked for it. The exception
        # carries the status we were about to wait for.
        status = e.status
      rescue Errno::ECHILD
        # Something else reaped it, so there is nothing left to ask for. $?
        # may still hold it, and may just as easily hold someone else's.
      end
    end
  end
  status ||= self.process_status
  # Better a plain error than a NoMethodError raised from inside the handler
  # that exists to report the failure, which is what nil used to produce.
  raise StandardError, "Could not determine the exit status of the command" if status.nil?
  raise StandardError, "Process crashed" if status.signaled?
  status.exitstatus
rescue StandardError => e
  # Wrapping any error in FastlanePtyError to allow callers to see and use
  # the exit status that would usually get returned
  status ||= self.process_status
  raise FastlanePtyError.new(e, status&.exitstatus || e.exit_status, status)
end