xcoder 0.1.15 → 0.1.18

Sign up to get free protection for your applications and to get access to all the features.
Files changed (47) hide show
  1. data/.gitignore +6 -0
  2. data/.rbenv-version +1 -0
  3. data/.rvmrc +1 -1
  4. data/Gemfile +10 -2
  5. data/README.md +110 -9
  6. data/Rakefile +2 -2
  7. data/bin/xcoder +74 -0
  8. data/lib/xcode/builder.rb +1 -2
  9. data/lib/xcode/builder/base_builder.rb +231 -102
  10. data/lib/xcode/builder/build_parser.rb +146 -0
  11. data/lib/xcode/builder/project_target_config_builder.rb +2 -2
  12. data/lib/xcode/builder/scheme_builder.rb +29 -12
  13. data/lib/xcode/buildspec.rb +286 -0
  14. data/lib/xcode/configuration_list.rb +24 -24
  15. data/lib/xcode/deploy/ftp.rb +56 -0
  16. data/lib/xcode/deploy/kickfolio.rb +18 -0
  17. data/lib/xcode/deploy/s3.rb +38 -0
  18. data/lib/xcode/deploy/ssh.rb +43 -0
  19. data/lib/xcode/deploy/templates/index.rhtml +22 -0
  20. data/lib/xcode/deploy/templates/manifest.rhtml +31 -0
  21. data/lib/xcode/deploy/testflight.rb +32 -27
  22. data/lib/xcode/deploy/web_assets.rb +39 -0
  23. data/lib/xcode/info_plist.rb +16 -0
  24. data/lib/xcode/keychain.rb +33 -10
  25. data/lib/xcode/platform.rb +65 -0
  26. data/lib/xcode/project.rb +7 -3
  27. data/lib/xcode/provisioning_profile.rb +38 -2
  28. data/lib/xcode/scheme.rb +44 -17
  29. data/lib/xcode/shell/command.rb +79 -5
  30. data/lib/xcode/terminal_output.rb +116 -0
  31. data/lib/xcode/test/formatters/junit_formatter.rb +7 -2
  32. data/lib/xcode/test/formatters/stdout_formatter.rb +34 -25
  33. data/lib/xcode/test/parsers/kif_parser.rb +87 -0
  34. data/lib/xcode/test/parsers/ocunit_parser.rb +3 -3
  35. data/lib/xcode/version.rb +1 -1
  36. data/lib/xcode/workspace.rb +13 -5
  37. data/lib/xcoder.rb +33 -31
  38. data/spec/TestProject/TestProject.xcodeproj/project.pbxproj +1627 -1015
  39. data/spec/TestWorkspace.xcworkspace/contents.xcworkspacedata +7 -0
  40. data/spec/builder_spec.rb +87 -71
  41. data/spec/deploy_spec.rb +63 -0
  42. data/spec/ocunit_parser_spec.rb +1 -1
  43. data/xcoder.gemspec +3 -1
  44. metadata +95 -19
  45. data/lib/xcode/buildfile.rb +0 -101
  46. data/lib/xcode/shell.rb +0 -26
  47. data/spec/deploy_testflight_spec.rb +0 -27
@@ -0,0 +1,116 @@
1
+ require 'colorize'
2
+
3
+ module Xcode
4
+ module TerminalOutput
5
+ @@colour_enabled = true
6
+ @@log_level = :info
7
+
8
+ LEVELS = [
9
+ :error,
10
+ :warning,
11
+ :notice,
12
+ :info,
13
+ :debug
14
+ ]
15
+
16
+ def log_level
17
+ @@log_level
18
+ end
19
+
20
+ def self.log_level=(level)
21
+ raise "Unknown log level #{level}, should be one of #{LEVELS.join(', ')}" unless LEVELS.include? level
22
+ @@log_level = level
23
+ end
24
+
25
+ def self.included(base)
26
+ @@colour_supported = terminal_supports_colors?
27
+ end
28
+
29
+ def color_output= color_output
30
+ @@colour_enabled = color_output
31
+ end
32
+
33
+ def color_output?
34
+ @@colour_supported and @@colour_enabled
35
+ end
36
+
37
+ #
38
+ # Print an IO input interaction
39
+ #
40
+ def print_input message, level=:debug
41
+ return if LEVELS.index(level) > LEVELS.index(@@log_level)
42
+ puts format_lhs("", "", "<") + message, :default
43
+ end
44
+
45
+ #
46
+ # Print an IO output interaction
47
+ def print_output message, level=:debug
48
+ return if LEVELS.index(level) > LEVELS.index(@@log_level)
49
+ puts format_lhs("", "", ">") + message, :default
50
+ end
51
+
52
+ def print_system message, level=:debug
53
+ return if LEVELS.index(level) > LEVELS.index(@@log_level)
54
+ puts format_lhs("", "", "!") + message, :green
55
+ end
56
+
57
+ def format_lhs(left, right, terminator=":")
58
+ # "#{left.to_s.ljust(10)} #{right.rjust(6)}#{terminator} "
59
+ "#{right.to_s.rjust(7)}#{terminator} "
60
+ end
61
+
62
+ def print_task(task, message, level=:info, cr=true)
63
+ return if LEVELS.index(level) > LEVELS.index(@@log_level)
64
+
65
+ level_str = ""
66
+ case level
67
+ when :error
68
+ level_str = "ERROR"
69
+ color = :red
70
+ when :warning
71
+ level_str = "WARN"
72
+ color = :yellow
73
+ when :notice
74
+ level_str = "NOTICE"
75
+ color = :green
76
+ when :info
77
+ level_str = ""
78
+ color = :blue
79
+ else
80
+ color = :default
81
+ end
82
+
83
+ print format_lhs(task, level_str), color
84
+ print message, (level==:warning or level==:error or level==:notice) ? color : :default
85
+
86
+ if block_given?
87
+ yield
88
+ end
89
+
90
+ print "\n" if cr
91
+ end
92
+
93
+ def puts(text, color = :default)
94
+ color_params = color_output? ? color : {}
95
+ super(text.colorize(color_params))
96
+ end
97
+
98
+ def print(text, color = :default)
99
+ color_params = color_output? ? color : {}
100
+ super(text.colorize(color_params))
101
+ end
102
+
103
+ def self.terminal_supports_colors?
104
+ # No colors unless we are being run via a TTY
105
+ return false unless $stdout.isatty
106
+
107
+ # Check if the terminal supports colors
108
+ colors = `tput colors 2> /dev/null`.chomp
109
+ if $?.exitstatus == 0
110
+ colors.to_i >= 8
111
+ else
112
+ false
113
+ end
114
+ end
115
+ end
116
+ end
@@ -6,7 +6,7 @@ module Xcode
6
6
  module Formatters
7
7
  class JunitFormatter
8
8
  def initialize(dir)
9
- @dir = File.expand_path(dir)
9
+ @dir = File.expand_path(dir).to_s
10
10
  FileUtils.mkdir_p(@dir)
11
11
  end
12
12
 
@@ -42,12 +42,17 @@ module Xcode
42
42
  end
43
43
  end
44
44
 
45
- File.open("#{@dir}/TEST-#{report.name}.xml", 'w') do |current_file|
45
+ File.open(File.join(@dir, sanitize_filename("TEST-#{report.name}.xml")), 'w') do |current_file|
46
46
  current_file.write xml.target!
47
47
  end
48
48
 
49
49
  end # write
50
50
 
51
+ private
52
+ def sanitize_filename(filename)
53
+ filename.gsub(/[^0-9A-Za-z.\-]/, '_')
54
+ end
55
+
51
56
  end # JUnitFormatter
52
57
 
53
58
  end # Formatters
@@ -2,44 +2,53 @@ module Xcode
2
2
  module Test
3
3
  module Formatters
4
4
  class StdoutFormatter
5
+ include Xcode::TerminalOutput
5
6
 
6
- def initialize
7
+ def initialize(options = {})
7
8
  @errors = []
9
+ @test_count = 0
10
+ options.each { |k,v| self.send("#{k}=", v) }
8
11
  end
9
12
 
10
13
  def before(report)
11
- puts "Begin tests"
14
+ print_task :test, "Begin tests", :info
12
15
  end
13
16
 
14
17
  def after(report)
15
- puts "\n\nThe following failures occured:" if @errors.count>0
16
- @errors.each do |e|
17
- puts "[#{e.suite.name} #{e.name}]"
18
- e.errors.each do |error|
19
- puts " #{error[:message]}"
20
- puts " at #{error[:location]}"
21
- if error[:data].count>0
22
- puts "\n Test Output:"
23
- puts " > #{error[:data].join(" > ")}\n\n"
18
+ level = @errors.count>0 ? :error : :info
19
+ if @errors.count>0
20
+ print_task :test, "The following failures occured:", :warning
21
+ @errors.each do |e|
22
+ print_task :test, "[#{e.suite.name} #{e.name}]", :error
23
+ e.errors.each do |error|
24
+ print_task :test, " #{error[:message]}", :error
25
+ print_task :test, " at #{error[:location]}", :error
26
+ if error[:data].count>0
27
+ print_task :test, "\n Test Output:", :error
28
+ print_task :test, " > #{error[:data].join(" > ")}\n\n", :error
29
+ end
30
+ end
31
+
32
+ # if there is left over data in the test report, show that
33
+ if e.data.count>0
34
+ print_task :test, "\n There was this trailing output after the above failures", :error
35
+ print_task :test, " > #{e.data.join(" > ")}\n\n", :error
24
36
  end
25
- end
26
-
27
- # if there is left over data in the test report, show that
28
- if e.data.count>0
29
- puts "\n There was this trailing output after the above failures"
30
- puts " > #{e.data.join(" > ")}\n\n"
31
37
  end
32
38
  end
33
39
 
34
- puts "\n\nEnd tests (#{report.failed? ? 'FAILED' : 'PASSED'}). Took #{report.duration}s"
40
+ print_task :test, "Finished in #{report.duration} seconds", :info
41
+ print_task :test, "#{@test_count} tests, #{@errors.count} failures", report.failed? ? :error : :info
35
42
  end
36
43
 
37
44
  def before_suite(suite)
38
- print "#{suite.name}: "
45
+ print_task :test, "#{suite.name}: ", :info, false
39
46
  end
40
47
 
41
48
  def after_suite(suite)
42
- puts " [#{suite.total_passed_tests}/#{suite.tests.count}]"
49
+ color = (suite.total_passed_tests == suite.tests.count) ? :info : :error
50
+ #print_task :test, "#{suite.total_passed_tests}/#{suite.tests.count}", color
51
+ puts " [#{suite.total_passed_tests}/#{suite.tests.count}]", color
43
52
  end
44
53
 
45
54
  def before_test(test)
@@ -47,14 +56,14 @@ module Xcode
47
56
  end
48
57
 
49
58
  def after_test(test)
59
+ @test_count += 1
50
60
  if test.passed?
51
- print "."
61
+ print ".", :green
52
62
  elsif test.failed?
53
- print "F"
63
+ print "F", :red
54
64
  @errors << test
55
- end
56
- # puts "[#{test.suite.name} #{test.name}] << END"
57
- end
65
+ end
66
+ end
58
67
 
59
68
  end # StdoutFormatter
60
69
  end # Formatters
@@ -0,0 +1,87 @@
1
+ require 'xcode/test/report'
2
+ require 'time'
3
+
4
+ module Xcode
5
+ module Test
6
+ module Parsers
7
+
8
+ class KIFParser
9
+ attr_accessor :report, :builder
10
+
11
+ def initialize(report = Xcode::Test::Report.new)
12
+ @report = report
13
+ @awaiting_scenario_name = false
14
+ yield self if block_given?
15
+ end
16
+
17
+ def flush
18
+ @report.finish
19
+ end
20
+
21
+ def <<(piped_row)
22
+ if @awaiting_scenario_name
23
+ if match = piped_row.match(/\[\d+\:.+\]\s(.+)/)
24
+ name = match[1].strip
25
+ @report.add_suite name, Time.now
26
+ @awaiting_scenario_name = false
27
+ end
28
+ return
29
+ end
30
+
31
+ case piped_row.force_encoding("UTF-8")
32
+
33
+ when /BEGIN KIF TEST RUN: (\d+) scenarios/
34
+ @report.start
35
+
36
+ when /BEGIN SCENARIO (\d+)\/(\d+) \(\d+ steps\)/
37
+ @awaiting_scenario_name = true
38
+
39
+ when /END OF SCENARIO \(duration (\d+\.\d+)s/
40
+ @report.in_current_suite do |suite|
41
+ suite.finish(Time.now)
42
+ end
43
+
44
+ when /(PASS|FAIL) \((\d+\.\d+s)\): (.+)/
45
+ duration = $2.to_f
46
+ name = $3.strip
47
+ @report.in_current_suite do |suite|
48
+ test = suite.add_test_case(name)
49
+
50
+ if $1 == 'PASS'
51
+ test.passed(duration)
52
+ else
53
+ test.failed(duration)
54
+ end
55
+ end
56
+
57
+ when /KIF TEST RUN FINISHED: \d+ failures \(duration (\d+\.\d+)s\)/
58
+ @report.finish
59
+
60
+ when /(.*): error: -\[(\S+) (\S+)\] : (.*)/
61
+ message = $4
62
+ location = $1
63
+ @report.in_current_test do |test|
64
+ test.add_error(message, location)
65
+ end
66
+
67
+ # when /failed with exit code (\d+)/,
68
+ when /BUILD FAILED/
69
+ @report.finish
70
+
71
+ when /Segmentation fault/
72
+ @report.abort
73
+
74
+ when /the iPhoneSimulator platform does not currently support application-hosted tests/
75
+ raise "Application tests are not currently supported by the iphone simulator. If these are logic tests, try unsetting TEST_HOST in your project config"
76
+ else
77
+ @report.in_current_test do |test|
78
+ test << piped_row
79
+ end
80
+ end # case
81
+
82
+ end # <<
83
+
84
+ end # OCUnitParser
85
+ end # Parsers
86
+ end # Test
87
+ end # Xcode
@@ -7,17 +7,17 @@ module Xcode
7
7
 
8
8
  class OCUnitParser
9
9
  attr_accessor :report, :builder
10
-
10
+
11
11
  def initialize(report = Xcode::Test::Report.new)
12
12
  @report = report
13
13
  yield self if block_given?
14
14
  end
15
15
 
16
- def flush
16
+ def close
17
17
  @report.finish
18
18
  end
19
19
 
20
- def <<(piped_row)
20
+ def << piped_row
21
21
  case piped_row.force_encoding("UTF-8")
22
22
 
23
23
  when /Test Suite '(\S+)'.*started at\s+(.*)/
@@ -1,3 +1,3 @@
1
1
  module Xcode
2
- VERSION = "0.1.15"
2
+ VERSION = "0.1.18"
3
3
  end
@@ -15,9 +15,13 @@ module Xcode
15
15
  doc = Nokogiri::XML(open("#{@path}/contents.xcworkspacedata"))
16
16
  doc.search("FileRef").each do |file|
17
17
  location = file["location"]
18
- if matcher = location.match(/^group:(.+)$/)
18
+ if (matcher = location.match(/^group:(.+\.xcodeproj)$/i))
19
19
  project_path = "#{workspace_root}/#{matcher[1]}"
20
- @projects << Xcode::Project.new(project_path)
20
+ begin
21
+ @projects << Xcode::Project.new(project_path)
22
+ rescue => e
23
+ puts "Skipping project file #{project_path} referened by #{self} as it failed to parse"
24
+ end
21
25
  end
22
26
  end
23
27
  end
@@ -41,8 +45,8 @@ module Xcode
41
45
  # @return [Scheme] the specific scheme that matches the name specified
42
46
  #
43
47
  def scheme(name)
44
- scheme = schemes.select {|t| t.name == name.to_s}.first
45
- raise "No such scheme #{name}, available schemes are #{schemes.map {|t| t.name}.join(', ')}" if scheme.nil?
48
+ scheme = schemes.select {|t| t.name == name.to_s and t.parent == self}.first
49
+ raise "No such scheme #{name} in #{self}, available schemes are #{schemes.map {|t| t.to_s}.join(', ')}" if scheme.nil?
46
50
  yield scheme if block_given?
47
51
  scheme
48
52
  end
@@ -58,10 +62,14 @@ module Xcode
58
62
  #
59
63
  def project(name)
60
64
  project = @projects.select {|c| c.name == name.to_s}.first
61
- raise "No such project #{name}, available projects are #{@projects.map {|c| c.name}.join(', ')}" if project.nil?
65
+ raise "No such project #{name} in #{self}, available projects are #{@projects.map {|c| c.name}.join(', ')}" if project.nil?
62
66
  yield project if block_given?
63
67
  project
64
68
  end
69
+
70
+ def to_s
71
+ "#{name} (Workspace)"
72
+ end
65
73
 
66
74
  def describe
67
75
  puts "Workspace #{name} contains:"
@@ -1,90 +1,92 @@
1
1
  require 'find'
2
2
  require 'fileutils'
3
+ require 'xcode/terminal_output'
3
4
  require "xcode/version"
4
5
  require "xcode/project"
5
6
  require "xcode/info_plist"
6
- require "xcode/shell"
7
+ require 'xcode/shell/command'
7
8
  require 'plist'
8
9
  require 'xcode/keychain'
9
10
  require 'xcode/workspace'
10
- require 'xcode/buildfile'
11
+ require 'xcode/platform'
12
+ require 'multi_json'
11
13
 
12
14
  module Xcode
13
-
15
+
14
16
  @@projects = nil
15
17
  @@workspaces = nil
16
18
  @@sdks = nil
17
-
19
+
18
20
  #
19
21
  # Find all the projects within the current working directory.
20
- #
22
+ #
21
23
  # @return [Array<Project>] an array of the all the Projects found.
22
- #
24
+ #
23
25
  def self.projects
24
26
  @@projects = parse_projects if @@projects.nil?
25
- @@projects
27
+ @@projects
26
28
  end
27
-
29
+
28
30
  #
29
31
  # Find all the workspaces within the current working directory.
30
- #
32
+ #
31
33
  # @return [Array<Workspaces>] an array of all the Workspaces found.
32
- #
34
+ #
33
35
  def self.workspaces
34
36
  @@workspaces = parse_workspaces if @@workspaces.nil?
35
37
  @@workspaces
36
38
  end
37
-
39
+
38
40
  #
39
41
  # Find the project with the specified name within the current working directory.
40
- #
42
+ #
41
43
  # @note this method will raise an error when it is unable to find the project
42
44
  # specified.
43
- #
45
+ #
44
46
  # @param [String] name of the project (e.g. NAME.xcodeproj) that is attempting
45
47
  # to be found.
46
- #
48
+ #
47
49
  # @return [Project] the project found; an error is raise if a project is unable
48
50
  # to be found.
49
- #
51
+ #
50
52
  def self.project(name)
51
53
  name = name.to_s
52
-
54
+
53
55
  return Xcode::Project.new(name) if name=~/\.xcodeproj/
54
-
56
+
55
57
  self.projects.each do |p|
56
58
  return p if p.name == name
57
59
  end
58
60
  raise "Unable to find a project named #{name}. However, I did find these projects: #{self.projects.map {|p| p.name}.join(', ') }"
59
61
  end
60
-
62
+
61
63
  #
62
64
  # Find the workspace with the specified name within the current working directory.
63
- #
65
+ #
64
66
  # @note this method will raise an error when it is unable to find the workspace
65
67
  # specified.
66
- #
68
+ #
67
69
  # @param [String] name of the workspace (e.g. NAME.xcworkspace) that is attempting
68
70
  # to be found.
69
- #
71
+ #
70
72
  # @return [Project] the workspace found; an error is raise if a workspace is unable
71
73
  # to be found.
72
- #
74
+ #
73
75
  def self.workspace(name)
74
76
  name = name.to_s
75
-
77
+
76
78
  return Xcode::Workspace.new(name) if name=~/\.xcworkspace/
77
-
79
+
78
80
  self.workspaces.each do |p|
79
81
  return p if p.name == name
80
82
  end
81
83
  raise "Unable to find a workspace named #{name}. However, I did find these workspaces: #{self.workspaces.map {|p| p.name}.join(', ') }"
82
84
  end
83
-
85
+
84
86
  #
85
87
  # @param [String] dir the path to search for projects; defaults to using
86
88
  # the current working directory.
87
- #
89
+ #
88
90
  # @return [Array<Project>] the projects found at the specified directory.
89
91
  #
90
92
  def self.find_projects(dir='.')
@@ -99,17 +101,17 @@ module Xcode
99
101
  parse_sdks if @@sdks.nil?
100
102
  @@sdks.values.include? sdk
101
103
  end
102
-
104
+
103
105
  #
104
106
  # Available SDKs available on this particular system.
105
- #
107
+ #
106
108
  # @return [Array<String>] the available SDKs on the current system.
107
- #
109
+ #
108
110
  def self.available_sdks
109
111
  parse_sdks if @@sdks.nil?
110
112
  @@sdks
111
113
  end
112
-
114
+
113
115
  private
114
116
  def self.parse_sdks
115
117
  @@sdks = {}
@@ -126,7 +128,7 @@ module Xcode
126
128
  end
127
129
  end
128
130
  end
129
-
131
+
130
132
  def self.parse_workspaces(dir='.')
131
133
  projects = []
132
134
  Find.find(dir) do |path|