xcoder 0.1.15 → 0.1.18

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 (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|