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
@@ -34,6 +34,22 @@ module Xcode
34
34
  @plist['CFBundleVersion'] = version.to_s
35
35
  end
36
36
 
37
+ def identifier
38
+ @plist['CFBundleIdentifier']
39
+ end
40
+
41
+ def identifier=(identifier)
42
+ @plist['CFBundleIdentifier'] = identifier
43
+ end
44
+
45
+ def display_name
46
+ @plist['CFBundleDisplayName']
47
+ end
48
+
49
+ def display_name=(name)
50
+ @plist['CFBundleDisplayName'] = name
51
+ end
52
+
37
53
  def save
38
54
  File.open(@plist_location, 'w') {|f| f << @plist.to_plist}
39
55
  end
@@ -1,18 +1,12 @@
1
1
  module Xcode
2
2
 
3
3
  module Keychains
4
-
4
+
5
5
  #
6
6
  # Yield when the keychain is in the search path and remove it when the block returns
7
7
  #
8
8
  def self.with_keychain_in_search_path(kc, &block)
9
- keychains = self.search_path
10
- begin
11
- self.search_path = [kc] + keychains
12
- yield
13
- ensure
14
- self.search_path = keychains
15
- end
9
+ kc.in_search_path &block
16
10
  end
17
11
 
18
12
 
@@ -45,6 +39,8 @@ module Xcode
45
39
  end
46
40
 
47
41
  class Keychain
42
+ include Xcode::TerminalOutput
43
+
48
44
  attr_accessor :name, :path
49
45
 
50
46
  TEMP_PASSWORD = "build_keychain_password"
@@ -61,6 +57,27 @@ module Xcode
61
57
 
62
58
  yield(self) if block_given?
63
59
  end
60
+
61
+ def to_s
62
+ "Keychain(#{@name})"
63
+ end
64
+
65
+ #
66
+ # Installs this keychain in the head of teh search path and restores the original on
67
+ # completion of the block
68
+ #
69
+ # @param the block to be invoked with the modified search path
70
+ #
71
+ def in_search_path(&block)
72
+ keychains = Keychains.search_path
73
+ begin
74
+ Keychains.search_path = [self] + keychains
75
+ yield
76
+ ensure
77
+ Keychains.search_path = keychains
78
+ # print_task 'keychain', "Restored search path"
79
+ end
80
+ end
64
81
 
65
82
  #
66
83
  # Import the .p12 certificate file into the keychain using the provided password
@@ -88,8 +105,8 @@ module Xcode
88
105
  cmd << "find-certificate"
89
106
  cmd << "-a"
90
107
  cmd << "\"#{@path}\""
91
- data = cmd.execute(false).join("")
92
- data.scan /\s+"labl"<blob>="([^"]+)"/ do |m|
108
+ cmd.show_output = false
109
+ cmd.execute.join("").scan /\s+"labl"<blob>="([^"]+)"/ do |m|
93
110
  names << m[0]
94
111
  end
95
112
  names
@@ -132,6 +149,12 @@ module Xcode
132
149
  cmd << "\"#{path}\""
133
150
  cmd.execute
134
151
 
152
+ cmd = Xcode::Shell::Command.new "security"
153
+ cmd << "set-keychain-settings"
154
+ cmd << "-u"
155
+ cmd << "\"#{path}\""
156
+ cmd.execute
157
+
135
158
  kc = Xcode::Keychain.new(path)
136
159
  yield(kc) if block_given?
137
160
  kc
@@ -0,0 +1,65 @@
1
+ module Xcode
2
+
3
+ module Platforms
4
+ @@platforms = []
5
+
6
+ def self.[] sdk_name
7
+ supported.find {|p| p.sdk==sdk_name}
8
+ end
9
+
10
+ def self.find platform, version = nil
11
+ platform = supported.sort do
12
+ |a,b| a.version.to_f <=> b.version.to_f
13
+ end.find do |p|
14
+ p.platform==platform and (version.nil? or p.version==version)
15
+ end
16
+
17
+ raise "Unable to find a platform #{platform},#{version} - available platforms are #{supported.map{|p| p.sdk}.join(', ')}" if platform.nil?
18
+
19
+ platform
20
+ end
21
+
22
+ def self.supported
23
+ return @@platforms unless @@platforms.count==0
24
+
25
+ parsing = false
26
+ `xcodebuild -showsdks`.split("\n").each do |l|
27
+ l.strip!
28
+ if l=~/(.*)\s+SDKs:/
29
+ parsing = true
30
+ elsif l=~/^\s*$/
31
+ parsing = false
32
+ elsif parsing
33
+ l=~/([^\t]+)\t+\-sdk (.*)/
34
+ name = $1.strip
35
+ $2.strip=~/([a-zA-Z]+)(\d+\.\d+)/
36
+
37
+ platform = Platform.new name, $1, $2
38
+ @@platforms << platform
39
+ end
40
+ end
41
+
42
+ @@platforms
43
+ end
44
+
45
+ end
46
+
47
+ class Platform
48
+ attr_reader :platform, :name, :version
49
+
50
+ def initialize name, platform, version
51
+ @platform = platform
52
+ @name = name
53
+ @version = version
54
+ end
55
+
56
+ def sdk
57
+ "#{@platform}#{@version}"
58
+ end
59
+
60
+ def to_s
61
+ "#{name} #{sdk}"
62
+ end
63
+ end
64
+
65
+ end
@@ -294,7 +294,7 @@ module Xcode
294
294
 
295
295
  target.name = name
296
296
 
297
- build_configuration_list = @registry.add_object(ConfigurationList.configration_list)
297
+ build_configuration_list = @registry.add_object(ConfigurationList.configuration_list)
298
298
  target.build_configuration_list = build_configuration_list.identifier
299
299
 
300
300
  target.project = self
@@ -341,8 +341,8 @@ module Xcode
341
341
  end
342
342
  schemes.each do |s|
343
343
  puts " + scheme #{s.name}"
344
- puts " + Launch action => target:#{s.launch.target.name}, config:#{s.launch.name}" unless s.launch.nil?
345
- puts " + Test action => target:#{s.test.target.name}, config:#{s.test.name}" unless s.test.nil?
344
+ puts " + targets: #{s.build_targets.map{|t| t.name}}"
345
+ puts " + config: #{s.build_config}"
346
346
  end
347
347
  end
348
348
 
@@ -367,5 +367,9 @@ module Xcode
367
367
  registry
368
368
  end
369
369
 
370
+ def to_s
371
+ "#{name} (Project)"
372
+ end
373
+
370
374
  end
371
375
  end
@@ -1,5 +1,6 @@
1
1
  module Xcode
2
2
  class ProvisioningProfile
3
+ include Xcode::TerminalOutput
3
4
  attr_reader :path, :name, :uuid, :identifiers, :devices, :appstore
4
5
  def initialize(path)
5
6
 
@@ -9,6 +10,7 @@ module Xcode
9
10
  @identifiers = []
10
11
  @devices = []
11
12
  @appstore = true
13
+ @enterprise = false
12
14
 
13
15
  # TODO: im sure this could be done in a nicer way. maybe read out the XML-like stuff and use the plist -> json converter
14
16
  uuid = nil
@@ -19,6 +21,10 @@ module Xcode
19
21
  @appstore = false
20
22
  end
21
23
 
24
+ if input=~/<key>ProvisionsAllDevices<\/key>/
25
+ @enterprise = true
26
+ end
27
+
22
28
  if input=~/<key>ProvisionedDevices<\/key>.*?<array>(.*?)<\/array>/im
23
29
  $1.split(/<string>/).each do |id|
24
30
  next if id.nil? or id.strip==""
@@ -44,6 +50,10 @@ module Xcode
44
50
  def appstore?
45
51
  @appstore
46
52
  end
53
+
54
+ def enterprise?
55
+ @enterprise
56
+ end
47
57
 
48
58
  def self.profiles_path
49
59
  File.expand_path "~/Library/MobileDevice/Provisioning\\ Profiles/"
@@ -54,11 +64,27 @@ module Xcode
54
64
  end
55
65
 
56
66
  def install
57
- Xcode::Shell.execute("cp #{self.path} #{self.install_path}")
67
+ # Do not reinstall if profile is same and is already installed
68
+ return if (self.path == self.install_path.gsub(/\\ /, ' '))
69
+
70
+ ProvisioningProfile.installed_profiles.each do |installed|
71
+ if installed.identifiers==self.identifiers and installed.uuid==self.uuid
72
+ installed.uninstall
73
+ end
74
+ end
75
+
76
+ # print_task "profile", "installing #{self.path} with uuid #{self.uuid}", :info
77
+ cmd = Xcode::Shell::Command.new 'cp'
78
+ cmd << self.path
79
+ cmd << self.install_path
80
+ cmd.execute
58
81
  end
59
82
 
60
83
  def uninstall
61
- Xcode::Shell.execute("rm -f #{self.install_path}")
84
+ # print_task "profile", "removing #{self.install_path}", :info
85
+ cmd = Xcode::Shell::Command.new 'rm'
86
+ cmd << "-f #{self.install_path}"
87
+ cmd.execute
62
88
  end
63
89
 
64
90
  def self.installed_profiles
@@ -66,6 +92,16 @@ module Xcode
66
92
  ProvisioningProfile.new(file)
67
93
  end
68
94
  end
95
+
96
+ def self.find_installed_by_uuid uuid
97
+ ProvisioningProfile.installed_profiles.each do |p|
98
+ return p if p.uuid == uuid
99
+ end
100
+ end
101
+
102
+ def self.installed_profile(name)
103
+ self.installed_profiles.select {|p| p.name == name.to_s}.first;
104
+ end
69
105
 
70
106
  end
71
107
  end
@@ -1,11 +1,16 @@
1
1
  require 'nokogiri'
2
2
 
3
3
  module Xcode
4
+
5
+ class SchemeAction
6
+ attr_accessor :config, :name
7
+ end
4
8
 
5
9
  # Schemes are an XML file that describe build, test, launch and profile actions
6
10
  # For the purposes of Xcoder, we want to be able to build and test
7
11
  class Scheme
8
- attr_reader :parent, :path, :name, :build_config, :build_targets
12
+ attr_reader :parent, :path, :name, :build_targets, :test_targets
13
+ attr_accessor :build_config, :archive_config, :test_config
9
14
 
10
15
  #
11
16
  # Parse all the schemes given the current project.
@@ -19,21 +24,21 @@ module Xcode
19
24
  #
20
25
  def self.find_in_workspace(workspace)
21
26
  schemes = find_in_path(workspace, workspace.path)
22
-
27
+
23
28
  # Project level schemes
24
29
  workspace.projects.each do |project|
25
- schemes+=project.schemes
30
+ schemes+=find_in_path(workspace, project.path)
26
31
  end
27
-
32
+
28
33
  schemes
29
34
  end
30
-
35
+
31
36
  # Parse all the scheme files that can be found at the given path. Schemes
32
37
  # can be defined as `shared` schemes and then `user` specific schemes. Parsing
33
38
  # the schemes will load the shared ones and then the current acting user's
34
39
  # schemes.
35
40
  #
36
- #
41
+ #
37
42
  # @param project or workspace in which the scheme is contained
38
43
  # @return [Array<Scheme>] the shared schemes and user specific schemes found
39
44
  # within the project/workspace at the path defined for schemes.
@@ -43,22 +48,35 @@ module Xcode
43
48
  Xcode::Scheme.new(parent: parent, root: path, path: scheme_path)
44
49
  end
45
50
  end
46
-
51
+
47
52
  def initialize(params={})
48
53
  @parent = params[:parent]
49
54
  @path = File.expand_path params[:path]
50
55
  @root = File.expand_path(File.join(params[:root],'..'))
51
56
  @name = File.basename(path).gsub(/\.xcscheme$/,'')
52
- doc = Nokogiri::XML(open(@path))
53
-
57
+ doc = Nokogiri::XML(open(@path))
58
+
54
59
  parse_build_actions(doc)
55
60
  end
56
-
57
- # Returns a builder for building this scheme
61
+
62
+ #
63
+ # @return a builder for building this scheme
64
+ #
58
65
  def builder
59
66
  Xcode::Builder::SchemeBuilder.new(self)
60
67
  end
61
68
 
69
+ def to_s
70
+ "#{name} (Scheme) in #{parent}"
71
+ end
72
+
73
+ #
74
+ # @return true if the scheme is testable, false otherwise
75
+ #
76
+ def testable?
77
+ !@test_config.nil?
78
+ end
79
+
62
80
  private
63
81
 
64
82
  #
@@ -84,25 +102,34 @@ module Xcode
84
102
  def self.current_user_schemes_paths(root)
85
103
  Dir["#{root}/xcuserdata/#{ENV['USER']}.xcuserdatad/xcschemes/*.xcscheme"]
86
104
  end
87
-
105
+
88
106
  def target_from_build_reference(buildableReference)
89
107
  project_name = buildableReference['ReferencedContainer'].gsub(/^container:/,'')
90
108
  target_name = buildableReference['BlueprintName']
91
- project_path = File.join @root, project_name
92
- project = Xcode.project project_path
109
+ project_path = File.join @root, project_name
110
+ project = Xcode.project project_path
93
111
  project.target(target_name)
94
112
  end
95
-
113
+
96
114
  def parse_build_actions(doc)
97
115
  # Build Config
98
116
  @build_targets = []
117
+ @test_targets = []
99
118
 
100
119
  @build_config = doc.xpath("//LaunchAction").first['buildConfiguration']
120
+ @archive_config = doc.xpath("//ArchiveAction").first['buildConfiguration']
121
+
122
+ if doc.xpath("//TestAction/Testables/TestableReference/BuildableReference").children.count>0
123
+ @test_config = doc.xpath("//TestAction").first['buildConfiguration']
124
+ doc.xpath("//TestAction/Testables/TestableReference/BuildableReference").each do |ref|
125
+ @test_targets << target_from_build_reference(ref)
126
+ end
127
+ end
101
128
 
102
129
  build_action_entries = doc.xpath("//BuildAction//BuildableReference").each do |ref|
103
- @build_targets << target_from_build_reference(ref)
130
+ @build_targets << target_from_build_reference(ref)
104
131
  end
105
132
  end
106
133
 
107
134
  end
108
- end
135
+ end
@@ -1,14 +1,32 @@
1
1
  require 'set'
2
+ require 'tmpdir'
3
+ require 'tempfile'
4
+ require 'pty'
2
5
 
3
6
  module Xcode
4
7
  module Shell
8
+
9
+ class ExecutionError < StandardError;
10
+ attr_accessor :output
11
+ def initialize(message, output=nil)
12
+ super message
13
+ @output = output
14
+ end
15
+ end
16
+
5
17
  class Command
6
- attr_accessor :env, :cmd, :args
18
+ include Xcode::TerminalOutput
19
+ attr_accessor :env, :cmd, :args, :show_output, :output_dir, :log_to_file, :output
7
20
 
8
21
  def initialize(cmd, environment={})
9
- @cmd = cmd
22
+ @cmd = cmd.to_s
10
23
  @args = []
11
24
  @env = environment
25
+ @show_output = true
26
+ @pipe = nil
27
+ @output = []
28
+ @output_dir = Dir.tmpdir
29
+ @log_to_file = false
12
30
  end
13
31
 
14
32
  def <<(arg)
@@ -32,9 +50,65 @@ module Xcode
32
50
  # to_s==obj.to_s
33
51
  Set.new(obj.to_a) == Set.new(self.to_a)
34
52
  end
35
-
36
- def execute(show_output=true, &block) #:yield: output
37
- Xcode::Shell.execute(self, show_output, &block)
53
+
54
+ #
55
+ # Attach an output pipe.
56
+ #
57
+ # This can be any object which responds to puts and close
58
+ def attach(pipe)
59
+ @pipe = pipe
60
+ @show_output = false
61
+ end
62
+
63
+ #
64
+ # Execute the given command
65
+ #
66
+ def execute(&block) #:yield: output
67
+ print_output self.to_s, :debug
68
+ # print_task 'shell', self.to_s, :debug if show_output
69
+ begin
70
+ output_file_name = File.join(@output_dir, "xcoder-#{@cmd}-#{Time.now.strftime('%Y%m%d-%H%M%S')}")
71
+
72
+ File.open(output_file_name, "w") do |file|
73
+ PTY.spawn(to_s) do |r, w, child_pid|
74
+ r.sync
75
+ r.each_line do |line|
76
+ file << line
77
+
78
+ print_input line.gsub(/\n$/,''), :debug if @show_output
79
+
80
+ if @pipe.nil?
81
+ # DEPRECATED
82
+ yield(line) if block_given?
83
+ else
84
+ @pipe << line
85
+ end
86
+
87
+ @output << line
88
+ end
89
+ Process.wait(child_pid)
90
+ end
91
+ end
92
+
93
+ raise ExecutionError.new("Error (#{$?.exitstatus}) executing '#{to_s}'", @output) if $?.exitstatus>0
94
+
95
+ if @log_to_file
96
+ print_system "Captured output to #{output_file_name}", :notice
97
+ else
98
+ File.delete(output_file_name)
99
+ end
100
+
101
+ @output
102
+ rescue Xcode::Shell::ExecutionError => e
103
+ print_system "Captured output to #{output_file_name}", :notice
104
+ print_system "Cropped #{e.output.count - 10} lines", :notice if @output.count>10
105
+ @output.last(10).each do |line|
106
+ print_output line.strip, :error
107
+ end
108
+ raise e
109
+ ensure
110
+ @pipe.close unless @pipe.nil?
111
+ end
38
112
  end
39
113
 
40
114
  end