Skip to content

Commit

Permalink
Timeout and retry xcodebuild -showBuildSettings (fastlane#5188)
Browse files Browse the repository at this point in the history
  • Loading branch information
fcy authored and mfurtak committed Sep 7, 2016
1 parent c439e7e commit c38388d
Show file tree
Hide file tree
Showing 2 changed files with 100 additions and 8 deletions.
55 changes: 47 additions & 8 deletions fastlane_core/lib/fastlane_core/project.rb
Original file line number Diff line number Diff line change
Expand Up @@ -219,7 +219,17 @@ def build_xcodebuild_showbuildsettings_command
def build_settings(key: nil, optional: true)
unless @build_settings
command = build_xcodebuild_showbuildsettings_command
@build_settings = Helper.backticks(command, print: false)

# xcode might hang here and retrying fixes the problem, see fastlane#4059
begin
timeout = FastlaneCore::Project.xcode_build_settings_timeout
retries = FastlaneCore::Project.xcode_build_settings_retries
@build_settings = FastlaneCore::Project.run_command(command, timeout: timeout, retries: retries, print: !self.xcodebuild_list_silent)
rescue Timeout::Error
UI.crash!("xcodebuild -showBuildSettings timed-out after #{timeout} seconds and #{retries} retries." \
" You can override the timeout value with the environment variable FASTLANE_XCODEBUILD_SETTINGS_TIMEOUT," \
" and the number of retries with the environment variable FASTLANE_XCODEBUILD_SETTINGS_RETRIES ")
end
end

begin
Expand Down Expand Up @@ -282,12 +292,11 @@ def raw_info(silent: false)
return @raw if @raw

command = build_xcodebuild_list_command
UI.important(command) unless silent

# xcode >= 6 might hang here if the user schemes are missing
begin
timeout = FastlaneCore::Project.xcode_list_timeout
@raw = FastlaneCore::Project.run_command(command, timeout: timeout)
@raw = FastlaneCore::Project.run_command(command, timeout: timeout, print: !silent)
rescue Timeout::Error
UI.user_error!("xcodebuild -list timed-out after #{timeout} seconds. You might need to recreate the user schemes." \
" You can override the timeout value with the environment variable FASTLANE_XCODE_LIST_TIMEOUT")
Expand All @@ -304,13 +313,43 @@ def self.xcode_list_timeout
end

# @internal to module
# runs the specified command and kills it if timeouts
# @raises Timeout::Error if timeout is passed
def self.xcode_build_settings_timeout
(ENV['FASTLANE_XCODEBUILD_SETTINGS_TIMEOUT'] || 10).to_i
end

# @internal to module
def self.xcode_build_settings_retries
(ENV['FASTLANE_XCODEBUILD_SETTINGS_RETRIES'] || 3).to_i
end

# @internal to module
# runs the specified command and kills it if timeouts, optionally retries before killing
# @raises Timeout::Error if timeout is passed after all retry attempts
# @returns the output
# Note: currently affected by fastlane/fastlane_core#102
def self.run_command(command, timeout: 0)
# Note: - currently affected by https://github.com/fastlane/fastlane/issues/1504
# - retry feature added to solve https://github.com/fastlane/fastlane/issues/4059
def self.run_command(command, timeout: 0, retries: 0, print: true)
require 'timeout'
@raw = Timeout.timeout(timeout) { `#{command}`.to_s }

UI.command(command) if print

result = ''

tries = 1
begin
Timeout.timeout(timeout) do
result = `#{command}`.to_s # Using Helper.backticks doesn't work. Timeout don't times out and the command hangs forever
end
rescue Timeout::Error
raise if tries >= retries

UI.verbose("Command timed out, trying again...")

tries += 1
retry
end

return result
end

private
Expand Down
53 changes: 53 additions & 0 deletions fastlane_core/spec/project_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -300,6 +300,48 @@ def within_a_temp_dir
end
end

describe 'Project.xcode_build_settings_timeout' do
before do
ENV['FASTLANE_XCODEBUILD_SETTINGS_TIMEOUT'] = nil
end
it "returns default value" do
expect(FastlaneCore::Project.xcode_build_settings_timeout).to eq(10)
end
it "returns specified value" do
ENV['FASTLANE_XCODEBUILD_SETTINGS_TIMEOUT'] = '5'
expect(FastlaneCore::Project.xcode_build_settings_timeout).to eq(5)
end
it "returns 0 if empty" do
ENV['FASTLANE_XCODEBUILD_SETTINGS_TIMEOUT'] = ''
expect(FastlaneCore::Project.xcode_build_settings_timeout).to eq(0)
end
it "returns 0 if garbage" do
ENV['FASTLANE_XCODEBUILD_SETTINGS_TIMEOUT'] = 'hiho'
expect(FastlaneCore::Project.xcode_build_settings_timeout).to eq(0)
end
end

describe 'Project.xcode_build_settings_retries' do
before do
ENV['FASTLANE_XCODEBUILD_SETTINGS_RETRIES'] = nil
end
it "returns default value" do
expect(FastlaneCore::Project.xcode_build_settings_retries).to eq(3)
end
it "returns specified value" do
ENV['FASTLANE_XCODEBUILD_SETTINGS_RETRIES'] = '5'
expect(FastlaneCore::Project.xcode_build_settings_retries).to eq(5)
end
it "returns 0 if empty" do
ENV['FASTLANE_XCODEBUILD_SETTINGS_RETRIES'] = ''
expect(FastlaneCore::Project.xcode_build_settings_retries).to eq(0)
end
it "returns 0 if garbage" do
ENV['FASTLANE_XCODEBUILD_SETTINGS_RETRIES'] = 'hiho'
expect(FastlaneCore::Project.xcode_build_settings_retries).to eq(0)
end
end

describe "Project.run_command" do
def count_processes(text)
`ps -aef | grep #{text} | grep -v grep | wc -l`.to_i
Expand Down Expand Up @@ -332,6 +374,17 @@ def count_processes(text)
expect(count_processes(text)).to eq(count)
# you would be expected to be able to see the number of processes go back to count right away.
end

it "retries and kills" do
text = "NEEDSRETRY"
cmd = "ruby -e 'sleep 3; puts \"#{text}\"'"

expect(FastlaneCore::Project).to receive(:`).and_call_original.exactly(3).times

expect do
FastlaneCore::Project.run_command(cmd, timeout: 1, retries: 3)
end.to raise_error(Timeout::Error)
end
end

describe 'xcodebuild_list_silent option' do
Expand Down

0 comments on commit c38388d

Please sign in to comment.